Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I use real objects or mocks in unit tests with Immutables?

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.

like image 781
ddreian Avatar asked Dec 08 '22 17:12

ddreian


1 Answers

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.

like image 102
GhostCat Avatar answered Dec 10 '22 05:12

GhostCat