PHPUnit: How to mock today's date without passing it as an argument?

这一生的挚爱 提交于 2020-01-02 01:01:18

问题


I am testing a method on my class that does some date checking. The problem is that the method depends on today's date (which changes every day), which makes this difficult to test. How can I mock today's date so that my tests will still pass tomorrow?


回答1:


I know nothing about PHP, but in both Java and C# I would pass in a "clock" of some description - not today's date itself, but an object which you can ask for the current date/time. Then in unit tests you can pass in an object which can give any date you want - including one that's hard-coded into the tests.

Does that work in PHP too?




回答2:


If your interest is to not pass in the date to preserve the external interface, then a good way to do this is to use a "seam" to provide the date:

class MyClass {
  public function toBeTested() {
    $theDate = $this->getDate();
    ...
  }

  protected function getDate() {
    return date();
  }
}

In general use, this class just works normally. Then, in your unit testing, instead of testing MyClass, you extend MyClass with an inner class that overrides the getDate() function:

class MyTest extends phpunittestcase (sorry, writing this freeform, syntax is not exact!!) {

  static $testDate;

  public function testToBeTested() {
    //set the date to be used
    MyTest::testDate = '1/2/2000';
    $classUnderTest = new MyClassWithDate();
    $this->assertEquals('expected', $classUnderTest->toBeTested());

  }

  //just pass back the expected date
  class MyClassWithDate extends MyClass {
    protected function getDate() {
      return MyTest::testDate;
    }
  }
}

In this code, you test against your extension of the real class, but your extension overrides the seam function (getDate()), and returns back the date that you want to use for this particular test.

Again, sorry if there are some egregious syntax errors, this was written freehand.




回答3:


While Jon's answer is the "right way," another option is to use the runkit extension to temporarily replace the date() and/or time() functions with ones that return a fixed value for the test.

  1. Make sure to set runkit.internal_override in php.ini so you can rename built-in functions.
  2. Rename the original function using runkit_function_rename.
  3. Rename your mock function with the original's name.
  4. Test.
  5. Rename your mock back.
  6. Rename the original back.

Here's some completely untested code to help with this:

function mock_function($original, $mock) {
    runkit_function_rename($original, $original . '_original');
    runkit_function_rename($mock, $original);
}

function unmock_function($original, $mock) {
    runkit_function_rename($original, $mock);
    runkit_function_rename($original . '_original', $original);
}

You should use these from within the setUp() and tearDown() methods to make sure you don't interfere with other tests that follow.




回答4:


I know you don't want to pass it in as an argument. But maybe you can rethink this ...

When being passed as a parameter from the outside, the date is not an insignificant technical detail, but a significant functional rule. Don't you need any of the following?

  • Although the current date can be the regular use case, the rule might be applicable to another date. Then your code would be more general, and work in a later use case with no modification. That happens to me regularly ...
  • Several codes could use the current date in an algorithm. Because the computer speed is not infinite, several would get a different instant ... Is that logical functionally? Or would using the same instant (for example, the instant your user pressed the "Fire" button) be more accurate? Think how you might request these times in your database later on, if they are all different in your database, even if they represent the same instant for your user!



回答5:


I couldn't rename twice as David says, so I got it like:

function mockDate()
{
    runkit_function_rename('date', 'test_date_override');
    runkit_function_add('date','$format=NULL,$timestamp=NULL,$locale=NULL', 'return DATEMOCK;');
}

function unmockDate()
{
    runkit_function_remove('date');
    runkit_function_rename('test_date_override', 'date');
}


来源:https://stackoverflow.com/questions/7210851/phpunit-how-to-mock-todays-date-without-passing-it-as-an-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!