phpunit Test Doubles (Mocks and Stubs) Simple stubbing


Example

Sometimes there are sections of code that are difficult to test, such as accessing a database, or interacting with the user. You can stub out those sections of code, allowing the rest of the code to be tested.

Let's start with a class that prompts the user. For simplicity, it has only two methods, one that actually prompts the user (which would be used by all the other methods) and the one we are going to test, which prompts and filters out only yes and no answers. Please note this code is overly simplistic for demonstration purposes.

class Answer
{
    // prompt the user and check if the answer is yes or no, anything else, return null
    public function getYesNoAnswer($prompt) {

        $answer = $this->readUserInput($prompt);

        $answer = strtolower($answer);
        if (($answer === "yes") || ($answer === "no")) {
            return $answer;
        } else {
            return null;
        }

    }

    // Simply prompt the user and return the answer
    public function readUserInput($prompt) {
        return readline($prompt);
    }

}

To test getYesNoAnswer, the readUserInput needs to be stubbed out to mimic answers from a user.

class AnswerTest extends PHPUnit_Framework_TestCase
{

    public function test_yes_no_answer() {

        $stub = $this->getMockBuilder(Answer::class)
                ->setMethods(["readUserInput"])
                ->getMock();

        $stub->method('readUserInput')
            ->will($this->onConsecutiveCalls("yes","junk"));

        // stub will return "yes"
        $answer = $stub->getYesNoAnswer("Student? (yes/no)");
        $this->assertSame("yes",$answer);

        // stub will return "junk"
        $answer = $stub->getYesNoAnswer("Student? (yes/no)");
        $this->assertNull($answer);


    }

}

The first line of code creates the stub and it uses getMockBuilder instead of createMock. createMock is a shortcut for calling getMockBuilder with defaults. One of these defaults is to stub out all the methods. For this example, we want to test getYesNoAnswer, so it can't be stubbed out. The getMockBuilder invokes setMethods to request that only readUserInput be stubbed out.

The second line of code creates the stubbing behavior. It stubs out readUserInput method and sets up two return values upon subsequent calls, "yes" followed by "junk".

The third and fourth lines of code test getYesNoAnswer. The first time, the fake person responds with "yes" and the tested code correctly returns "yes", since it is a valid selection. The second time, the fake person responds with "junk" and the tested code correctly returns null.