If I have to test a service that uses a mutable entity I would build the smallest object that I need (a real one) and pass it to my service. Example:
User joe = new User();
joe.setEmail("[email protected]");
resetPasswordService.resetPassword(joe);
verif(emailServiceMock).sendEmail("[email protected]", "Your password has been reset!");
Obviously User has lots of fields but I do not set them since resetPasswordService
does not need them. This is very refactor-friendly since if I rename a User field that is not the email this test will not be changed.
The problem appears when I try to do the same with an Immutables object. I will stick with the same example and turn User from an entity into an immutable.
@Value.Immutable
public abstract class User {
public abstract String getEmail();
public abstract PostalAddress getPostalAddress();
//more fields
}
User joe = new ImmutableUserBuilder().email("[email protected]").build();
resetPasswordService.resetPassword(joe);
verif(emailServiceMock).sendEmail("[email protected]", "Your password has been reset!");
java.lang.IllegalStateException: Cannot build User, some of required attributes are not set [postalAddress, signupDate, city, ....]
This fails in the builder when it tries to build the object. So what should I do?
- Use a mock for User and have it return mocks even if every time a mock returns a mock a fairy dies
- Create a testing DSL and have some sort of factory to build the entire User tree structure with all the fields I don't need? Seems heavy and not so refactor-friendly. This makes the requirements of the test not so transparent.
- Make all the fields in User
@Nullable
and have the builder not validate the object? This would expose me to the risk of having incomplete objects in production, right? - some other option I missed?
I know Users should be entities and not immutable value objects. I used User in this example since it is easy to understand.
Simple answer: you only use mocks if you have to.
Meaning: when you need to either control the behavior of an object in ways that the "real" class doesn't support. Or when you have to verify calls on the mock.
So: when you can write a test case that does what you want it to do without using mocking - then go for that.
Mock frameworks are tools. You don't use them because you can, but because they solve a problem for you that you otherwise can't address (easily).
Beyond that: as explained, the default should be to avoid mocks. On the other hand, programming is always about balancing efforts and "return on investment". That is why I used the word easily above. When it turns out that using a mock results in writing down 2, 3 easy-to-comprehend lines of code ... but using "the real" class is much more complicated (or relies on certain implicit assumption about how that class works) - then using a mock can be the better choice.
In that sense, the answer is: don't take answers and rules as golden standard. In the end, this is always about human judgement.