Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Travesty of a Mockery... - PHPSW

Travesty of a Mockery... - PHPSW

A whirlwind tour of test doubles, what they are, how you can use them, when you should use them.

Dave Marshall

August 14, 2015
Tweet

More Decks by Dave Marshall

Other Decks in Programming

Transcript

  1. Test Doubles A replacement for a real dependency, used to

    enable, ease or improve testing of the SUT $double = new NullUserRepository(); $sut = new UserService($double);
  2. Generated Doubles: Tools » phpunit/phpunit-mock-objects » phpspec/prophecy » mockery/mockery »

    phake/phake » codeception/aspect-mock » php-vcr/php-vcr » many others...
  3. Dummies Used whenever we need to pass arguments to a

    constructor or method, where those arguments should not be used while exercising the SUT
  4. Dummies: Values # SUT BCryptPasswordEncoder public function encodePassword($raw, $salt) {

    if ($this->isPasswordTooLong($raw)) { throw new BadCredentialsException( 'Invalid password.' ); } # ... }
  5. Dummies: Values /** @test */ public function it_refuses_a_long_password() { $encoder

    = new BCryptPasswordEncoder(31); $this->setExpectedException( "BadCredentialsException" ); $encoder->encodePassword( str_repeat("a", 4097), "dummy salt that will not be used" ); }
  6. Dummies: Objects # SUT AuthenticationProvider public function __construct(EncoderInterface $encoder) {

    $this->encoder = $encoder; } public function checkAuthentication(User $user, $presentedPassword) { if ("" === $presentedPassword) { throw new BadCredentialsException( 'The presented password cannot be empty. '); } # do something with the encoder }
  7. Dummies: Objects by hand class DummyEncoder implements EncoderInterface { public

    function isPasswordValid($hashedPassword, $plainTextPassword, $salt) { throw new RuntimeException("Not implemented"); } }
  8. Dummies: Objects by hand /** @test */ public function it_throws_on_an_empty_password()

    { $authProvider = new AuthenticationProvider( new DummyEncoder() ); $this->setExpectedException("BadCredentialsException"); $authProvider->checkAuthentication(new User(), ""); }
  9. Dummies: Objects with mockery /** @test */ public function it_throws_immediately_on_an_empty_password()

    { $authProvider = new AuthenticationProvider( Mockery::mock("EncoderInterface") ); $this->setExpectedException("BadCredentialsException"); $authProvider->checkAuthentication(new User(), ""); }
  10. Fakes "Replace a component that the system under test (SUT)

    depends on with a much lighter-weight implementation." -- http://xunitpatterns.com We use a Fake whenever our SUT has a dependency that is unavailable, slow or simply makes testing difficult.
  11. Fakes: Fake Object class PlaintextPasswordEncoder extends BasePasswordEncoder { public function

    encodePassword($raw, $salt) { return sprintf("%s{%s}", $raw, $salt); } }
  12. Fakes: Fake Object /** @test */ public function it_validates_a_password() {

    $encoder = new PlaintextPasswordEncoder(); $authProvider = new AuthenticationProvider($encoder); $user = new User([ 'password' => 'pass{salt}', 'salt' => 'salt' ]); $this->assertNull( $authProvider->checkAuthentication($user, "pass") ); }
  13. Fakes » Usually hand coded » Low coupling between SUT

    and test » May need to expose state for verification » Can get complicated to maintain
  14. Stubs Stubs are used to control the indirect inputs to

    the SUT, by providing canned responses to calls made during the test
  15. Stubs: Stub Object /** @test */ function it_gets_a_user() { $employee

    = new Employee(); $repo = $this->getMock('EmployeeRepository'); $repo->method('find') ->with($id = 123); ->willReturn($employee); $sut = new UserService($repo); $this->assertEquals( $employee, $sut->get($id) ); }
  16. Mocks: Example /** @test */ function it_persists_a_user() { $employee =

    new Employee(); $repo = $this->prophesize('EmployeeRepository'); $repo->add($employee)->shouldBeCalled(); $sut = new UserService($repo->reveal()); $sut->post($employee); $this->getProphet()->checkPredictions(); }
  17. Mocks: Example /** @test */ function it_persists_a_user() { $employee =

    new Employee(); $repo = $this->prophesize('EmployeeRepository'); $repo->add($employee)->shouldBeCalled(); $sut = new UserService($repo->reveal()); $sut->post($employee); }
  18. Mocks » Verify behaviour of the SUT by requiring you

    to provide details of any interaction they should expect » Can act as stubs, returning values » Can lead to overspecification, leading to brittle tests
  19. Mocks: When to use them » When you need to

    verify the behaviour of the SUT, because it's not easy to do so via the final state of the SUT » When you're working outside-in, discovering new interfaces as you develop the SUT
  20. Spies: Example /** @test */ function it_persists_a_user() { $employee =

    new Employee(); $repo = $this->prophesize('EmployeeRepository'); $repo->add($employee)->shouldBeCalled(); $sut = new UserService($repo->reveal()); $sut->post($employee); }
  21. Spies: Example /** @test */ function it_persists_a_user() { $employee =

    new Employee(); $repo = $this->prophesize('EmployeeRepository'); $sut = new UserService($repo->reveal()); $sut->post($employee); $repo->add($employee)->shouldHaveBeenCalled(); }
  22. Spies » Record interactions, allow verification afterwards » More familiar

    workflow: Arrange -> Act -> Assert Given -> When -> Then » Can reveal more intent, by hiding irrelevant calls » Less likely to expose smells by hiding details » Less precise, leading to less fragile tests » Debugging can be harder
  23. Spies: When to use them (instead of mocks) » If

    you would like to » To help communicate the intentions of your test » If you can't predict a value ahead of time » If a mock wouldn't be able to report a failed expectation (edge case)
  24. Verifiying State “We inspect the state of the SUT after

    it has been exercised and compare it to the expected state.” Verifying Behaviour “We capture the indirect outputs of the SUT as they occur and compare them to the expected behavior.”
  25. Behaviour Verification: Precision “Following Einstein, a specification should be as

    precise as possible, but not more precise” -- Mock Roles, not Objects Find a balance between precision and flexibility. Greater precision leads to over-specified, brittle tests. Allow Queries, expect Commands Ignore collaborators that are irrelevant for the current test
  26. When to mock “Never mock values, sometimes mock entities, but

    mock services freely.” » http://blog.thecodewhisperer.com/2010/09/14/when- is-it-safe-to-introduce-test-doubles
  27. When to Mock: Don't mock values Just create them. $email

    = m::mock("Email"); $email->shouldReceive("getDomain")->andReturn("example.com"); $email = Email::fromString("[email protected]");
  28. When to Mock: Try not to mock concrete classes »

    It hides relationships between objects » A form of over-specification » If describing an interface doesn't seem right, don't use a test double $employee = m::mock("Employee"); $employee->shouldReceive("getEmail")->andReturn("[email protected]"); # use object mothers or test data builders for complicated object graphs etc $employee = ExampleEmployee::withEmail("[email protected]");
  29. When to Mock: Only mock types you own » Or

    try to only mock types you trust » Use mocking to derive interfaces for the things your SUT needs » Write adapters for the things you don't trust