Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. Travesty of a
    Mockery...

    View Slide

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

    View Slide

  3. Terminology
    -- http://xkcd.com/503/

    View Slide

  4. Terminology

    View Slide

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

    View Slide

  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);

    View Slide

  7. Creating Test Doubles

    View Slide

  8. Hand built

    View Slide

  9. Generated

    View Slide

  10. Generated Doubles: Tools
    » phpunit/phpunit-mock-objects
    » phpspec/prophecy
    » mockery/mockery
    » phake/phake
    » codeception/aspect-mock
    » php-vcr/php-vcr
    » many others...

    View Slide

  11. Configurable

    View Slide

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

    View Slide

  13. Dummies

    View Slide

  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

    View Slide

  15. Dummies: Values
    # SUT BCryptPasswordEncoder
    public function encodePassword($raw, $salt)
    {
    if ($this->isPasswordTooLong($raw)) {
    throw new BadCredentialsException(
    'Invalid password.'
    );
    }
    # ...
    }

    View Slide

  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"
    );
    }

    View Slide

  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
    }

    View Slide

  18. Dummies: Objects by hand
    class DummyEncoder implements EncoderInterface
    {
    public function isPasswordValid($hashedPassword,
    $plainTextPassword,
    $salt)
    {
    throw new RuntimeException("Not implemented");
    }
    }

    View Slide

  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(), "");
    }

    View Slide

  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(), "");
    }

    View Slide

  21. Fakes

    View Slide

  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.

    View Slide

  23. Fakes: Fake Object
    class PlaintextPasswordEncoder extends BasePasswordEncoder
    {
    public function encodePassword($raw, $salt)
    {
    return sprintf("%s{%s}", $raw, $salt);
    }
    }

    View Slide

  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")
    );
    }

    View Slide

  25. Fakes
    » Usually hand coded
    » Low coupling between SUT and test
    » May need to expose state for verification
    » Can get complicated to maintain

    View Slide

  26. Stubs

    View Slide

  27. Stubs
    Stubs are used to control the indirect inputs to the
    SUT, by providing canned responses to calls made
    during the test

    View Slide

  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)
    );
    }

    View Slide

  29. Mocks

    View Slide

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

    View Slide

  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();
    }

    View Slide

  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);
    }

    View Slide

  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

    View Slide

  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

    View Slide

  35. Spies

    View Slide

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

    View Slide

  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);
    }

    View Slide

  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();
    }

    View Slide

  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

    View Slide

  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)

    View Slide

  41. Considerations

    View Slide

  42. Test First or Test Last

    View Slide

  43. State or Behaviour

    View Slide

  44. Classicist vs Mockist

    View Slide

  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.”

    View Slide

  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

    View Slide

  47. Behaviour Verification
    Mock all the things?
    16 Mock objects
    18 method expectations

    View Slide

  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

    View Slide

  49. 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]");

    View Slide

  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("[email protected]");
    # use object mothers or test data builders for complicated object graphs etc
    $employee = ExampleEmployee::withEmail("[email protected]");

    View Slide

  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

    View Slide

  52. When to Mock: Mock Ports

    View Slide

  53. Questions/Feedback
    » joind.in/14900
    » @davedevelopment
    » [email protected]

    View Slide