Given a unit test class that needs to use a specific service, it seems that there are different ways to fake the behaviour of the service, such as using a mocking framework, or implement a stub class.
For instance, a service to read/write to disk:
public interface FileServiceInterface {
public void write(String text);
public String read(String fileName);
}
I might have a class that fakes its behaviour and later use it in the test class, injecting the Stub instead of the real implementation:
public class FileServiceStub implements FileServiceInterface {
ConcurrentHashMap<String, String> content = new ConcurrentHashMap<>();
public void write(String fileName, String text) {
content.put(fileName, text);
}
public String read(String fileName) {
return content.get(fileName);
}
}
Other option is to let Mockito (for example) intercept the calls to the service directly in the test class:
public class TestExample {
@Mock
private FileServiceImpl service; // A real implementation of the service
@Test
void doSomeReadTesting() {
when(service.read(any(String.class))).thenReturn("something");
...
}
}
I would like to know which of these alternatives is the best (or currently most accepted) approach, and if there's any other/better option. Thanks.
CodePudding user response:
Short answer: depends on the use case when you need to check a behavior use Mock otherwise in the case of state-based test use Stub.
In state verification you have the object under testing perform a certain operation, after being supplied with all necessary stubs. When it ends, you examine the state of the object and verify it is the expected one.
In behavior verification, you specify exactly which methods are to be invoked, thus verifying not that the ending state is correct, but that the sequence of steps performed was correct.
Martin Fowler described a great comparison between Stubs and Mocks in the
article Mocks Aren't Stubs.
There are several types of pretend object used in place of a real object for testing purposes:
- Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
- Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
- 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.
- 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.
- Mocks objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
In your case we have Fake vs Mock object type.
They all look like real objects, but unlike Mocks, others types do not have pre-programmed expectations that could fail your test. A Stub or Fake only cares about the final state - not how that state was derived.
So there we have two different design styles of testing.
State based tests are more black-boxed. They don’t actually care how the System Under Test(SUT) achieves its result, as long as it is correct. This makes them more resistant to changes and less coupled to design.
But occasionally you do run into things that are really hard to use state verification on, even if they aren't awkward collaborations. A great example of this is a cache. The whole point of a cache is that you can't tell from its state whether the cache hit or missed - this is a case where behavior verification would be a wise choice.
Also good thing about Mock is the ability to define relaxed constraints on the expectation - you can use any()
, anyString()
, withAnyArguments()
. It is flexible.