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);
Dummies: Values # SUT BCryptPasswordEncoder public function encodePassword($raw, $salt) { if ($this->isPasswordTooLong($raw)) { throw new BadCredentialsException( 'Invalid password.' ); } # ... }
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" ); }
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 }
Dummies: Objects by hand class DummyEncoder implements EncoderInterface { public function isPasswordValid($hashedPassword, $plainTextPassword, $salt) { throw new RuntimeException("Not implemented"); } }
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(), ""); }
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.
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
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
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
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)
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.”
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
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
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]");
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