5 ways to write

Better Mocks


By @JeroenDeDauw
www.EntropyWins.wtf


Use "page down" and "page up" to navigate

#1: dedicated creation functions


public function myNiceTest() {
    $serviceStub = $this->createMock(Service::class);

    $serviceStub->expects($this->once())
                ->method('getNyan')
                ->willThrow(new NotEnoughNyanException());
    $serviceStub->expects($this->once())
                ->method('getMoreNyan')
                ->willThrow(new NotEnoughNyanException());

    $classUnderTest = new SomeClass($serviceStub);
    $classUnderTest->doThings();

    $this->someAssertionStuff(/*...*/);
}

						

public function myNiceTest() {
    $classUnderTest = new SomeClass($this->newThrowingService());
    $classUnderTest->doThings();

    $this->someAssertionStuff(/*...*/);
}
						

#2: know your test doubles

  • Dummy - does nothing
  • Stub - fixed return
  • Spy - records calls
  • Mock - makes assertions
  •  
  •  
  • Fake - implementation

#3: avoid call count binding


$serviceStub = $this->createMock(Service::class);

$serviceStub->expects($this->once())
            ->method('getNyan')
            ->willReturn('~=[,,_,,]:3');
						

$serviceStub->expects($this->any())
            ->method('getNyan')
            ->willReturn('~=[,,_,,]:3');
						

#4: avoid method name binding


interface LoggerInterface {
    public function log($level, $message, array $context = array());

    public function emergency($message, array $context = array());
    public function alert($message, array $context = array());
    public function critical($message, array $context = array());
    public function error($message, array $context = array());
    public function warning($message, array $context = array());
    public function notice($message, array $context = array());
    public function info($message, array $context = array());
    public function debug($message, array $context = array());
}
						

$logger->expects($this->once())
       ->method('emrgency');
						

$logger->expects($this->once())
       ->method($this->anything());
						

#5: use your own test doubles


$serviceStub = $this->createMock(Service::class);

$serviceStub->expects($this->any())
            ->method('getNyan')
            ->willReturn('~=[,,_,,]:3');

$classUnderTest = new SomeClass($serviceStub);
						

class StubService implements Service {
    public function getNyan(): string {
        return '~=[,,_,,]:3';
    }
}

$classUnderTest = new SomeClass(new StubService());
						

#5: use your own test doubles


class LoggerSpy extends \Psr\Log\AbstractLogger {
    private $logCalls = [];

    public function log($level, $message, array $context = []) {
        $this->logCalls[] = [$level, $message, $context];
    }

    public function getLogCalls() {
        return $this->logCalls;
    }
}
						

#5: use your own test doubles


class InMemoryKittenRepository implements KittenRepository {
    private $kittens = [];

    public function save(Kitten $kitten) {
        $this->kittens[$kitten->getId()] = $kitten;
    }

    public function getKittenById(KittenId $id): Kitten {
        if (array_key_exists($id, $this->kittens)) {
            return $this->kittens[$kittenId];
        }
        throw new KittenNotFound();
    }
}
						

Bonus cat

Cattribution

Questions?

www.EntropyWins.wtf

@JeroenDeDauw