Save 37% off PRO during our Black Friday Sale! »

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.

B423daa9c89538f919aec9f86f767821?s=128

Dave Marshall

August 14, 2015
Tweet

Transcript

  1. Travesty of a Mockery...

  2. Travesty of a Mockery... » @davedevelopment » CTO at Childcare.co.uk

    » Podcaster at ThatPodcast.io
  3. Terminology -- http://xkcd.com/503/

  4. Terminology

  5. Dependencies $doc = new UserRepository(); $sut = new UserService($doc);

  6. 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);
  7. Creating Test Doubles

  8. Hand built

  9. Generated

  10. Generated Doubles: Tools » phpunit/phpunit-mock-objects » phpspec/prophecy » mockery/mockery »

    phake/phake » codeception/aspect-mock » php-vcr/php-vcr » many others...
  11. Configurable

  12. Test Doubles » Dummy » Fake » Stub » Mock

    » Spy
  13. Dummies

  14. Dummies Used whenever we need to pass arguments to a

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

    if ($this->isPasswordTooLong($raw)) { throw new BadCredentialsException( 'Invalid password.' ); } # ... }
  16. 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" ); }
  17. 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 }
  18. Dummies: Objects by hand class DummyEncoder implements EncoderInterface { public

    function isPasswordValid($hashedPassword, $plainTextPassword, $salt) { throw new RuntimeException("Not implemented"); } }
  19. 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(), ""); }
  20. 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(), ""); }
  21. Fakes

  22. 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.
  23. Fakes: Fake Object class PlaintextPasswordEncoder extends BasePasswordEncoder { public function

    encodePassword($raw, $salt) { return sprintf("%s{%s}", $raw, $salt); } }
  24. 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") ); }
  25. Fakes » Usually hand coded » Low coupling between SUT

    and test » May need to expose state for verification » Can get complicated to maintain
  26. Stubs

  27. Stubs Stubs are used to control the indirect inputs to

    the SUT, by providing canned responses to calls made during the test
  28. 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) ); }
  29. Mocks

  30. Mocks Mocks are used to verify the indirect outputs of

    the SUT
  31. 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(); }
  32. 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); }
  33. 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
  34. 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
  35. Spies

  36. Spies Spies are used to observe the indirect outputs of

    the SUT
  37. 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); }
  38. 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(); }
  39. 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
  40. 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)
  41. Considerations

  42. Test First or Test Last

  43. State or Behaviour

  44. Classicist vs Mockist

  45. 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.”
  46. 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
  47. Behaviour Verification Mock all the things? 16 Mock objects 18

    method expectations
  48. 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
  49. When to Mock: Don't mock values Just create them. $email

    = m::mock("Email"); $email->shouldReceive("getDomain")->andReturn("example.com"); $email = Email::fromString("dave@example.com");
  50. 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("dave@atst.io"); # use object mothers or test data builders for complicated object graphs etc $employee = ExampleEmployee::withEmail("dave@atst.io");
  51. 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
  52. When to Mock: Mock Ports

  53. Questions/Feedback » joind.in/14900 » @davedevelopment » dave@atst.io