Lens

Mocks

Unit testing works well for methods that have controlled dependencies and no side effects. Let’s write a class that depends on an uncontrolled external resource and has dangerous side effects. Here’s an example:

src/Terminal.php
<?php

namespace Example;

class Terminal
{
    public function read()
    {
        return rtrim(fgets(STDIN), "\n");
    }

    public function write($line)
    {
        fputs(STDOUT, $line . "\n");
    }
}

This “Terminal” class communicates with the user by sending text messages through the terminal. When the user is typing in a response, this code will block the execution of the script. That blocking behavior will make it a real challenge to run our tests!

But first, let’s see it in action. Let’s write a tiny class that uses the “Terminal”:

src/Speller.php
<?php

namespace Example;

class Speller
{
    private $terminal;

    public function __construct(Terminal $terminal)
    {
        $this->terminal = $terminal;
    }

    public function start()
    {
        $this->terminal->write("Type a word:");

        $word = $this->terminal->read();
        $spelling = $this->spell($word);

        $this->terminal->write("The word \"{$word}\" is spelled: {$spelling}!");
    }

    private function spell($word)
    {
        return implode('-', str_split(strtoupper($word)));
    }
}

And let’s add an autoloader to load these new classes:

autoload.php
<?php

spl_autoload_register(
    function ($class) {
        $namespacePrefix = 'Example\\';
        $namespacePrefixLength = strlen($namespacePrefix);

        if (strncmp($class, $namespacePrefix, $namespacePrefixLength) !== 0) {
            return;
        }

        $relativeClassName = substr($class, $namespacePrefixLength);
        $filePath = __DIR__ . '/src/' . strtr($relativeClassName, '\\', '/') . '.php';

        if (is_file($filePath)) {
            include $filePath;
        }
    }
);

And let’s add a script, so we can run the new tool:

speller.php
<?php

namespace Example;

require 'autoload.php';

$terminal = new Terminal();
$speller = new Speller($terminal);

$speller->start();

And now we can run the spelling tool:

Run the spelling tool:
php speller.php
# Type a word:
# perfectly
# The word "perfectly" is spelled: P-E-R-F-E-C-T-L-Y!

Yes, try running this little spelling tool on the command line! Can you think of a word that it can’t spell?

Download this example

Okay! But how will we test it? The “Terminal” class will halt the execution of our tests! And the “Speller” won’t run without the “Terminal”!

What we need is a mock: A mock will provide the same external behavior as the real “Terminal” class, but without actually reading and writing to the terminal, and without blocking execution:

tests/Speller.php
<?php

namespace Example;

// Test
$speller = new Speller($terminal);
$speller->start();

// Input
$terminal = new Terminal();

// Output
$terminal->write('Type a word:');
$terminal->read(); // return 'perfectly';
$terminal->write('The word "perfectly" is spelled: P-E-R-F-E-C-T-L-Y!');

Like everything in Lens, mocks are simple. Everything in the “Input” section is mocked automatically. And we provided the expected behavior under the “Output” section.

That’s all there is to it!

Run Lens
lens
# Passed tests: 1
Download this example