In the advice of Mockito about how to write good tests, it is written that we should not mock value objects (https://github.com/mockito/mockito/wiki/How-to-write-good-tests#dont-mock-value-objects). They even say
Because instantiating the object is too painful !? => not a valid
reason.
But I don’t understand. What if we really don’t care about what that object contain?
Let’s just say that I have a value object class “Foo” that doesn’t have simple constructors (it has an all-args constructor and contains objects that also need all-args constructors).
Let’s also say I have the following service:
public class MyService {
private SomeService someService;
private OtherService otherService;
public void myAwesomeMethod() {
Foo foo = someService.getFoo();
someOtherService.doStuff(foo);
}
}
And I need to test that method. Clearly here I don’t care about what foo contains because I just pass it to another method. If I want to test it should I really create a foo object with nested constructors/builders? For me it would be really easy to mock foo
even if it is a POJO :
public class MyServiceTest {
@InjectMocks
MyService myService;
@Mock
private SomeService someService;
@Mock
private OtherService otherService;
@Test
public void testMyAwesomeMethod() {
Foo mockedFoo = Mockito.mock(Foo.class);
Mockito.when(someService.getFoo()).thenReturn(mockedFoo);
Mockito.verify(otherService).doStuff(mockedFoo);
}
}
But then it wouldn’t respect the guidelines given by Mockito. Is there another alternative?
10
Stubs (that you’ve called mocks in your question) exist for a reason: they make it possible to replace business code from the classes you depend on in the method you’re testing by some very basic statements which provide enough data for the tested method. Mocks, just like stubs, replace business logic by custom logic required for the tests.
-
If a POJO doesn’t have business code (i.e. if it’s also a data object), stubs and mocks would be useless. Using such data objects in tests should be quite easy. If the method needs a data object in input, just create one in the test.
Some data objects may be very large, containing dozens of properties. In some languages, you can partially initialize a data object by setting only the properties you need during the test; in other languages, you need to fully initialize it. If this is your case, creating a simple static method which creates the data object with the default values and reusing this method all over the tests could be a solution.
-
If a POJO contains business code, you should isolate your unit tests from it, like you would do for any other class. Here, stubs and mocks are fine, and it doesn’t make a difference if the mocked object is a POJO or something else.
6
The best line from your link in my opinion is
Don’t mock everything, it’s an anti-pattern
If everything is mocked, are we really testing the production code?
Don’t hesitate to not mock!
Usually you want to test your code with real inputs and expected outputs and usually these will be data structures or pojos/pocos
ie.
actual = myfunc(input)
Assert.AreEqual(actual, expected)
It doesn’t make sense to mock that input or output because its set up specifically for that test. You always care about it.
In your example you have no input, output or indeed logic to perform. Even if you have a real example where code just passes an object from one service to another, surely you want to test edge cases like nulls or a very large object? You should care about what that object is.
You might be running into a code smell.
Clearly here I don’t care about what foo contains because I just pass it to another method.
The thing that stands out to me in this example is that your code doesn’t even care that the object being exchanged is a Foo
.
public class MyService<T> {
private java.util.function.Supplier<? extends T> someService;
private java.util.function.Consumer<? super T> otherService;
public void myAwesomeMethod() {
T foo = someService.get();
someOtherService.accept(foo);
}
}
Then, in your unit test, instead of trying to use a Foo
, or to create a test double that you can use in place of a Foo
, you can inject a Supplier/Consumer pair.
Let’s just say that I have a value object class “Foo” that doesn’t have simple constructors (it has an all-args constructor and contains objects that also need all-args constructors).
Another common pattern is to use test data builders to create the values that you need. Test data builders typically use fluent interfaces to communicate within a test which details of the object are important in the context of the test.
@Test
public void testMyAwesomeMethod() {
Foo mockedFoo = new FooBuilder().anyOldFoo();
Mockito.when(someService.getFoo()).thenReturn(mockedFoo);
Mockito.verify(otherService).doStuff(mockedFoo);
}
Here, the interaction with the builder articulates that the behavior of this test shouldn’t depend on the specific foo value.
If you squint, you might see that the test is asserting a property
public void testMyAwesomeMethod() {
for (Foo candidate : allPossibleFoo()) {
Mockito.when(someService.getFoo()).thenReturn(candidate);
// ...
}
}
None of these builder approaches directly solves the problem of invoking the fool constructor; all the builder does is move that complexity out of the test.
You are somewhat correct.
When you mock an object, you typically stub out the methods to return canned values instead. But there isn’t really a good reason to do this for a value object, just use the value object instead.
However, in this particular scenario you aren’t stubbing out any of the methods. You are just verifying that the same object returned from one function gets passed to another. So you don’t get the disadvantages of mocking the value object.
Nevertheless, you almost certainly should have test code that constructs a Foo. The test for SomeService
should probably assertEquals
the result of getFoo
with some constructed Foo. The test for OtherService
should pass some constructed Foo
object to doStuff
. You’ve got to have code somewhere that constructs Foo objects for those tests, so you should be able to readily refactor that construction code into a place where it can be reused.
However, in general, it is suspicious that MyService
doesn’t want to call any methods on Foo.
This is often a sign that MyService
shouldn’t be dealing with Foo at all. If all MyService
does it take an object from SomeService
and pass it to OtherService
what’s the point of MyService
? Simplify your code and connect the two services directly.