I have the following method to test:
public List<MarkId> getMarkIdList(ICar carDoc) {
ICourseCar courseCarDoc = courseCarRep.get(carDoc);
List<MarkWag> markWagList = courseCarDoc.getMarks();
List<Integer> markIdList = new ArrayList<>();
for (MarkWag markWag : markWagList)
markIdList.add(markWag.getMarkId());
List<ICourseMark> courseMarkDocList = courseMarkRep.getList(markIdList);
List<MarkId> markIds = new ArrayList<>();
for (ICourseMark courseMark : courseMarkDocList)
markIds.add( new MarkId(courseMark.getMarkDescriptor()));
return markIds;
}
This is the unit test I created:
@RunWith(PowerMockRunner.class)
@SpringApplicationCourseuration(classes=SpringConf.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@PowerMockIgnore({"javax.management.*"})
public class CourseCarTest {
@Mock private CourseCarRepImpl courseCarRep;
@Mock private CourseMarkRepImpl courseMarkRep;
@Mock private CourseCarDoc courseCat;
@Mock private ICourseCar courseCarDoc;
@Mock MarkWag markWag;
List<MarkWag> markWagList = new ArrayList<>();
@Mock ICourseMark courseMark;
List<ICourseMark> courseMarkDocList = new ArrayList<>();
@Mock ICar carDoc;
@InjectMocks
@Autowired
@Qualifier("CourseCarBO")
private CourseCarBO courseCarBo;
@Before
public void setup() throws Exception {
markWagList.add(markWag);
courseMarkDocList.add(courseMark);
whenNew(CourseCarRepImpl.class).withAnyArguments().thenReturn(courseCarRep);
whenNew(CourseMarkRepImpl.class).withAnyArguments().thenReturn(courseMarkRep);
when(courseCarRep.get(anyInt())).thenReturn(courseCarDoc);
when(courseCarDoc.getMarks()).thenReturn(markWagList);
when(markWag.getMarkId()).thenReturn(1);
when(courseMarkRep.getList(anyList())).thenReturn(courseMarkDocList);
when(courseMark.getMarkDescriptor()).thenReturn(1);
}
@Test
public void testGetMarkIdList() {
courseCarBo.getMarkIdList(1);
verify(courseCarRep).get(anyInt());
}
}
My intention was to have an hybrid approach, both mocking some objects and injecting others with Spring. However I noticed that as I mock one of the objects, I have to mock the others that follow as they depend on each other. So it looks to me like it's not really possible to have an hybrid approach.
Can you spot anything wrong in the approach used in this unit test?
When I have to mock everything, it looks to me like if I'm not testing anything.
Is there a better way? Or my approach is correct, and mocking everything is the way to go?
Best Answer
You should begin by moving from procedural programming, which your code currently is, to OOP.
"I have objects," you may protest, but in fact your objects do not encapsulate anything. In a true OO world, your code should look like this:
The
get()
method ofcourseCarRep
returns aCourseCar
object, which already has everything it needs within it and thegetCourseMarkIdsAsList()
is a public method theCourseCar
class provides to hide the more complicated logic behind it (which is taking its list of CourseMarks and returning only their identifications).Which may bring up a question: Why do I need the
getMarkIdList(ICar)
method in the first place?So how do I unit test the current
getMarkIdList
method?Simple answer: you do not.
More elaborate answer: The procedure you are trying to test seems to be so high in your application layer it is no longer considered a single unit and therefore should not be tested using unit tests.
If you want to test that the method works, use integration testing. If you want to focus on unit testing, test the following objects and highlighted methods instead:
courseCarRep::get
courseCarDoc::getMarks
courseMarkRep::getList
courseMark::getMarkDescriptor
If you test, that those methods work under all conditions, you can be pretty sure, your procedure will work as well.
Trust. Your. Tests.
What should I (generally) really mock, when doing unit tests?
You should only mock the behaviour of objects which are necessary for the test to pass. Everything else should be replaced by a dummy or null (if possible).
Null values are a great indicator that something is unused. If I go through a unit test and see someone pass a
null
instead of an object, I safely assume that specific variable has nothing to do with the test itself, it is not part of it, and even if I replace thenull
object with an actual instance, the test results cannot change.Also try to remove the
new
operator from your business logic. Either replace it by factories, if you feel like you really need to create something, or use dependency injection.