Mocks, Stubs & Doubles

What

  • Mocking is used for protocol testing - it tests how you'll use an API, and how you'll react when the API reacts accordingly.

  • Ideally (in many cases at least), that API should be specified as an interface rather than a class - an interface defines a protocol, a class defines at least part of an implementation.

  • Test doubles turn integration tests into unit/isolation tests

  • https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html

  • https://martinfowler.com/articles/mocksArentStubs.html

  • https://javacodehouse.com/blog/mockito-tutorial/

  • https://www.codurance.com/publications/2019/04/08/introduction-to-test-doubles

  • http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html

mockito

  • https://stackoverflow.com/questions/22867680/how-does-mockito-mock-interfaces

  • https://www.toptal.com/java/a-guide-to-everyday-mockito

mocks

  • Cannot mock final class/methods using mockito 1.x, need latest version (use inline dependency)

How to control final classes

  • If class is final but in your scope of change

    • Add an interface to the class to be controlled, and create a test class that implements that interface, then inject it into the system under test

  • If class is final but not in your scope of change

    • Cannot add an interface

    • Can wrap the dependency (adapter), replace the call of the actual with the adapter in codebase and mock the adapter in the tests

How to verify non injected objects

  • When a object is instantiated in the class under test, and want to verify that this object is used, we can use argumentcaptor

  • Thus when we it is used (we check the captor is used in the mehtod we verify) then can grab use the object that was captured, use its method and assert on them

  • https://stackoverflow.com/questions/10598898/mockito-numberformat-mocking-nullpointer-in-when-method

stubs

  • provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test

  • Stubbing is the process of giving behavior to a function that otherwise has no behavior on its own—you specify to the function exactly what values to return (that is, you stub the return values).

  • ie From mockito: when(...).thenReturn(...)

  • typically done through mocking frameworks

  • stubbing is appropriate when you need a function to return a specific value to get the system under test into a certain state

  • each stubbed function should have a direct relationship with the test’s assertions.

  • real implementations or fakes are still preferred because they don’t expose implementation details and they give you more guarantees about the correctness of the code compared to stubbing

Limitations

Dummy

  • are passed around but never actually used. Usually they are just used to fill parameter lists.

Spy

  • are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.

Fakes

  • A fake is a lightweight implementation of an API that behaves similar to the real implementation but isn’t suitable for production

  • for example, an inmemory database, wiremock

  • Using a fake is often the ideal technique when you need to use a test double, but a fake might not exist for an object you need to use in a test, and writing one can be challenging because you need to ensure that it has similar behavior to the real implementation, now and in the future

  • Normally use an already written on

    • otherwise it is hard to create, as it must mimic the real implementation and must be maintained

Mocks

  • are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting

Interaction Testing

  • is a way to validate how a function is called without actually calling the implementation of the function. A test should fail if afunction isn’t called the correct way; for example, if the function isn’t called at all, it’s called too many times, or it’s called with the wrong arguments.

  • interaction testing is typically done through mocking frameworks

  • Interaction testing is sometimes called mocking

  • it is preferred to test code through state testing

  • it can only validate that certain functions are called as expected. It requires you to make an assumption about the behavior of the code; for example, “If database.save(item) is called, we assume the item will be saved to the database."

  • Leaks details to test

  • when to use

    • You cannot perform state testing because you are unable to use a real implementation or a fake

    • Differences in the number or order of calls to a function would cause undesired behavior

    • PREFER TO PERFORM INTERACTION TESTING ONLY FOR STATE-CHANGING FUNCTIONS

    • you should perform interaction testing only for functions that are state-changing

With state testing

  • you call the system under test and validate that either the correct value was returned or that some other state in the system under test was properly changed

  • it actually validates this assumption (such as by saving an item to a database and then querying the database to validate that the item exists)

  • if cannot state, use interaction testing, but also use integration testing

London/mockist school

chicago/classic school

Mocking frameworks

  • A mocking framework is a software library that makes it easier to create test doubles within tests;

    • it allows you to replace an object with a mock, which is a test double whose behavior is specified inline in a test.

  • The use of mocking frameworks reduces boilerplate because you don’t need to define a new class each time you need a test double.

why

  • Essential for testing and not want to call anything which can have consequences (ie state change (internally or externally), production calls, charges for services)

  • If you want to verify the behavior of the system under test (SUT), using mockito can be more effective as it allows you to verify interactions between the SUT and its collaborators.

Problems with mocking

  • When you mock something you’re removing all confidence in the integration between what you’re testing and what’s being mocked.

    • You will need to do separate tests for integration part, to see that it actually works

  • mocking is somewhat overused - often you're not really interested in the exact interaction, you really want a stub... but mocking framework can be used to create stubs, and you fall into the trap of creating brittle tests by mocking instead of stubbing. It's a hard balance to get right though.

  • When using verify (to check a dependency has been called) this ties the test to the implementation/structure of the code, rather than the behaviour

    • better to have stub injected, and assert on the stub

    • dependencies are part of the constructor and thus public

  • Can lead to mocking everything except the SUT

    • This can make test brittle

    • SUT is not necessarily a class, but can be a bunch of objects to test the behaviour

    • Over-reliance on mocks can lead to tests that are not representative of the real-world behavior of the system, resulting in tests that are less effective and less reliable.

  • SUT with lots of dependencies and interactions, and use of mocks can make tests more complex and unclear with lots of extra code

  • Leaks implementation details into your test

    • Ideally, a good test should need to change only if user-facing behavior of an API changes; it should remain unaffected by changes to the API’s implementation

  • Use of mocking frameworks can lead to false sense of security

    • MAke tests hard to maintain

    • makes refactoring harder, as tests will break

    • bug finding hard

  • test doubles, does not have exact sameness as the real implementation, so always some form of security that it is give correct evaluations

  • https://engineering.talkdesk.com/double-trouble-why-we-decided-against-mocking-498c915bbe1c

  • https://youtu.be/7AGQ9dhWCX0 Test Doubles Are A Scam – Matt Diephouse

  • Mocking can add additional processing time during the build process, slowing down the overall build time.

alternative - use real implementations

  • makes refactoring easier

  • known as classical testing

  • tests are more realistic, more confidence in the system

  • Using real implementations can cause your test to fail if there is a bug in the real implementation.

    • A real implementation is preferred if it is fast, deterministic, and has simple dependencies. For example, a real implementation should be used for a value object

    • for more complex code, using a real implementation often isn’t feasible

      • ie database or network calls, edge of app. these can be mocked

      • ie for nondeterministic code such as external servers, time, randommness

  • Can be slow

  • Need to construct the object graph for the SUT, can be complex

    • Should use the same mechanism as test, and adjust (ie factories) to replace mocked dependencies

alternative - use fake implementations

  • Instead of a real implementation, use an inmemory version

  • This a fake or stub

  • If there is a logic which mimics the actual impl in the fake, it needs to be tested to ensure it matches the real impl and be used with accuracy in the tests

  • https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a

  • https://chemaclass.medium.com/to-mock-or-not-to-mock-af995072b22e

Mocking third party

  • This should not be done, should avoid mocking third party code

  • https://youtu.be/v6hP2MXoVrI Don't Mock 3rd Party Code

Last updated