Mocking PHP functions in unit tests

烈酒焚心 提交于 2019-12-09 08:42:50

问题


I'm unit-testing some PHP code with SimpleTest and I've run into trouble. In my tests of a database class I want to be able to set an expectation for PHPs mysql functions. In my tests of a wrapper class for the mail function I want to mock PHPs mail function. These are just some examples.

The point is: I don't (always) want to test if my Mail class sends e-mail, I want to test how it calls the mail function. I want to be able to control what these functions return. I want to be able to test my Database class without needing a database, fixtures and that whole lot.

I've got some experience with testing Ruby code, and Test::Unit and RSpec make it very easy to test code in isolation. I'm new to testing PHP and it feels like I'm testing a lot more than I should need to, in order to get my tests to pass.

Is there a way in SimpleTest or PhpUnit or some other testing framework that makes this possible or easier?


回答1:


Not in an automated way. What you can do, is to write your code in a way such that external dependencies are wrapped in objects that are passed in from the outside. In your production environment you'll just wire up the real adapters, but during testing, you can wire it to stubs or mocks.

If you really insist, you can use the runkit extension which changes php's programming model so that you can redefine classes and functions at runtime. This is an external and nonstandard extensions however, so keept that in mind. The defacto standard is a manual approach as I described above.




回答2:


Here is an interesting article that writes about mocking global php functions. The author proposes a very creative solution to 'Mock' (kind off) the global php functions by overwriting the methods inside the namespace of the SUT (service under test).

Here code from an example in the blog post where the time function is mocked:

<?php

namespace My\Namespace;

use PHPUnit_Framework_TestCase;

/**
 * Override time() in current namespace for testing
 *
 * @return int
 */
function time()
{
    return SomeClassTest::$now ?: \time();
}

class SomeClassTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var int $now Timestamp that will be returned by time()
     */
    public static $now;

    /**
     * @var SomeClass $someClass Test subject
     */
    private $someClass;

    /**
     * Create test subject before test
     */
    protected function setUp()
    {
        parent::setUp();
        $this->someClass = new SomeClass;
    }

    /**
     * Reset custom time after test
     */
    protected function tearDown()
    {
        self::$now = null;
    }

    /*
     * Test cases
     */
    public function testOneHourAgoFromNoon()
    {
        self::$now = strtotime('12:00');
        $this->assertEquals('11:00', $this->someClass->oneHourAgo());
    }
    public function testOneHourAgoFromMidnight()
    {
        self::$now = strtotime('0:00');
        $this->assertEquals('23:00', $this->someClass->oneHourAgo());
    }
}

Not sure if this is a good way to do it but it surely works well and I think it is worth mentioning here. Could be some food for a discussion...




回答3:


There is a PHPUnit extension that uses runkit internally, and is capable to use invocation matchers, parameter constraints and everything a mocked object can do.

https://github.com/tcz/phpunit-mockfunction




回答4:


In PHP 5.3+ environment you may workaround the need to use runkit extension by hacking the namespaces. The only requirement in that the function calls do not use fully-qualified namespace like \mysql_query() - and they usually don't. Then you can define the same function in your test, by defining the test in a namespace, and PHP will call your function instead of the global one. Personally I use this approach to stub the time() function call. Here is a nice example with the mockery framework



来源:https://stackoverflow.com/questions/1244949/mocking-php-functions-in-unit-tests

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