The “system under test” is short for “whatever thing we are testing” and is always defined from the perspective of the test. When we are writing unit tests the system under test (SUT) is whatever class (a.k.a. CUT), object (a.k.a. OUT) or method(s) (a.k.a. MUT) we are testing; when we are writing customer tests, the SUT is probably the entire application (a.k.a. AUT) or at least a major subsystem of it. The parts of the application that we are not verifying in this particular test may still be involved as a depended-on component (DOC).
The term SUT means also a stage of maturity of the software, because a system test is the successor of integration test in the testing cycle.
There are very few classes that operate entirely in isolation. Usually they need other classes or objects in order to function, whether injected via the constructor or passed in as method parameters. These are known as Collaborators.
The generic term he uses is a Test Double (think stunt double). Test Double is a generic term for any case where you replace a production object for testing purposes.
There are five types of TDs, which are dummy, fake, mock, stub, spy.
It is used when a parameter is needed for the tested method but without actually needing to use the parameter.
Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
It’s not intended to be used in your tests and will have no effect on the behaviour.
It is used for providing the tested code with “indirect input”.
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.
Think of a Stub as a step up from a Dummy. It implements a required Interface, but instead of missing method bodies, it usually returns canned responses in order for you to make assertions on the output of the SUT.
It is used for verifying “indirect output” of the tested code, by asserting the expectations afterwards, without having defined the expectations before the tested code is executed.
Spies 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.
A Stub could also be used to maintain state, and make assertions, when a Stub does this it is also known as a Test Spy.
It is used as a simpler implementation, e.g. using an in-memory database in the tests instead of doing real database access.
Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example).
The simplest way to think of a Fake is as a step up from a Stub. This means not only does it return values, but it also works just as a real Collaborator would.
It is used for verifying “indirect output” of the tested code, by first defining the expectations before the tested code is executed.
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.
Whilst all of the other types of Test Double build on each other, gradually adding more functionality, Mocks are something totally different.
Until now, all of our test doubles have made assertions on state. Mocks are test doubles that make assertions on behaviour.
Typically, using a Mock will consist of three phases: Creating the instance, Defining the behaviour, Asserting the calls.
Classical vs Mockist
There are two schools of thought on Test Doubles: Classical, and Mockist. Put simply, people who use the Classical style only use Test Doubles when there is a compelling reason to do so, such as external dependencies, whilst a Mockist would mock everything, all the time.
The advantage of using the Classical style is that you don’t have to worry about internal behaviour of your objects, which is what OOP is all about. When you change an object and it’s collaborators your tests will still pass, but this comes at the sacrifice of speed and isolation.
The advantage of using the Mockist style is that your tests are isolated and can run much faster, but at the sacrifice of coupling your tests to the internal behaviour of your objects and their collaborators.