If you haven’t been mocking yet, check this out. There are other tools in Groovy using DSLs but if you want to stick to Java, Mockito is by far the best BDD-style mock unit testing tool.
Traditional examples provide a List mocking example or foo/bar example which I find it mostly annoying since it doesn’t teach you how to solve real problems. And the documentation is verbose and crams with unnecessary features confusing us further. In this tutorial, I’ll dive straight into a relevant example of mock testing Daos in Service layer. I’m not a big fan of Daos anymore (after the advent of JPA), however for the purposes of this tutorial, it’ll be easier to explain.
Consider a Profile entity (like facebook user profile) with daos/services: ProfileDao/JPAProfileDao, ProfileService/ProfileServiceImpl. To complicate things further, say you are using DTOs since it’s a client server app with a GWT front end. You might need a simple mapper class. Let’s call it ProfileMapper/ProfileMapperImpl.
@Service("profileService")
public class ProfileServiceImpl implements ProfileService {
private final ProfileDao profileDao;
private final ProfileMapper profileMapper;
@Inject
public ProfileServiceImpl(final ProfileDao profileDao, final ProfileMapper profileMapper) {
this.profileDao = profileDao;
this.profileMapper = profileMapper;
}
@Transactional
public void create(ProfileDto profile) throws DuplicateProfileException {
if (profile == null) {
throw new IllegalArgumentException("profile cannot be null.");
}
Profile profile = profileMapper.toEntity(profile);
// set default state as active
profile .setIsActive(true);
try {
profileDao.save(profile);
}
catch (DuplicateKeyException e) {
logger.warn("Unable to save profile with userId: " + profile.getUserId() + " already exists.");
throw new DuplicateProfileException();
}
logger.debug("Profile saved successfully.");
}
@Transactional(readOnly = true)
public ProfileDto read(String userId) throws InvalidProfileException {
validateProfileInfo(userId);
ProfileDto profileDto = null;
Profile profile = profileDao.read(userId);
if (profile != null) {
profileDto = profileMapper.fromEntity(profile);
logger.debug("Profile retrieved successfully.");
}
else {
logger.debug("...");
}
return profileDto;
}
...
}
Now, in order to test ProfileService, you need to mock out ProfileMapper and ProfileDao, right? Let’s see how we do this in a simple JUnit 4.4+ test.
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class ProfileServiceTest {
@Mock
ProfileDao profileDao;
@Mock
ProfileMapper profileMapper;
// Can't use @InjectMocks because Mockito doesn't support constructor injection
// As a workaround use @Before below to initialize service with stubs
ProfileService profileService;
@Before
public void init() {
profileService = new ProfileServiceImpl(profileDao, profileMapper);
}
@Test
public void testRead() throws InvalidProfileException {
// Simulate InvalidProfileException
try {
profileService.read("");
fail(); // should never execute
}
catch (InvalidProfileException e) {
e.getMessage(); // expected
}
// Given
Profile profile = TestData.createTestProfile("userid"); // test utility to create a test Profile
when(profileDao.read("userid")).thenReturn(profile);
when(profileMapper.fromEntity(profile )).thenReturn(TestData.createTestProfileDto("userid"));
// When
ProfileDto dto = profileService.read("userid");
// Then
assertEquals(dto.getUserId(), "userid");
}
}
Explanation:
1) Wrapping your test with @RunWith(MockitoJUnitRunner.class) helps create the mock initialization through annotation scanning (@Mock, @InjectMocks)
2) Add @Mock annotations for the components to mock. In this case profileDao, profileMapper
3) As of 1.8.5, Mockito doesn’t provide @InjectMock with construction injection. @Before test initializer is a workaround to inject ProfileService with mocks. Note that this will not be required if ProfileService has setter/getter injection of dependencies. However, I prefer JSR330 construction injection which is a best practice .
4) The actual @Test class with mockito stubs. Let’s dig deep into this.
As a template, it’s a good practice to view tests like a BDD style test.
// Given // When // Then
The above template forces you to think on the explicit conditions during which we run our unit test.
Next, we need to figure out a way to stub our mock component’s methods,
when(operation).thenReturn(object)
In our example we want to mock profileDao.read(userId). So that translates to
when(profileDao.read("userid")).thenReturn(profile);
Note that we’re letting the mocked dao return a profile object that we just created in the previous step. This will be 50% of most of your mocking use cases.
The next common used use case is to simulate exceptions from the mocked classes. Consider a test for create(…) that simulates the DuplicateProfileException.
@Test
public void testCreateDuplicate() {
// Given
ProfileDto dto = TestData.createTestProfileDto("userid");
Profile profile = TestData.createTestProfile("userid");
when(profileMapper.toEntity(dto)).thenReturn(profile);
doThrow(new DuplicateKeyException("duplicate profile")).when(profileDao).save(profile);
// When
try {
profileService.create(dto);
assertEquals(1, 2); // should never execute
}
// Then
catch (DuplicateProfileException e) {
e.getMessage();
}
}
Notice the line
doThrow(new DuplicateKeyException("duplicate profile")).when(profileDao).save(profile);
Quite simple, isn’t it?
If you’ve read so far, you can succuessfully write mock unit tests for 80% of your use cases. There are other advanced features like finding redundant invocations, spying on objects to simulate partial mocking, most of which I don’t see implementing for majority of my use cases.
7 Comments
Nice read & learned something new from it!
Although I don’t get why you would write something like
assertEquals(1, 2); // should never execute
You could change it to make it a bit more understandable
http://junit.sourceforge.net/javadoc/org/junit/Assert.html#fail(java.lang.String)
Good point. I was too lazy too read the api.
Thank you for your example of using mockito.
I would split the “testRead” method in several methods testing only one behaviour, for example “testEmptyReadThrowsInvalidProfileException” and “testValidReadReturnsCorrectDto”
I would also test that an exception is thrown using @Test(expected=YourException.class): see http://junit.sourceforge.net/javadoc/org/junit/Test.html#expected() and http://junit.sourceforge.net/doc/cookbook/cookbook.htm (at the end of the page) for an example.
Gr8 article and I def. I agree the annoying foo/bar doesn’t give anything solid especially when you run into errors
TestData.createTestProfile(“userid”); Here still we are incurring some manual labour to create a class and method to return us a profile and to me it looks like not mocking completely, but may be in future what I would like to have is that you just specify the data somehow and get the object created. Thanks
Best and easy example so far
Keep the good work up!
A great example.
I agree with your comments about other examples using lists since they didn’t reflect Martin Fowler’s discussion about the ‘subject under test’ as distinct the collaborator (http://martinfowler.com/articles/mocksArentStubs.html), in list examples I think they have collapsed the ‘subject under test’ and collaborator together as one in order to showcase mockito easier but this cause confusion about the right technique when mocking.
I am not sure where Spring is coming in here, that’s why I was looking at your article in the first place(google, “Spring and Mockito”), your example is a straight Mockito test i.e. there is no Spring dependency injection (maybe I have the wrong end of the stick?)
None the less, a good Mockito example.
@DAVID – Spring comes into play via the @Service annotations and/or @Inject/@Transactional. Not sure what in Spring you would want to mock; the approach, though, is similar.
2 Trackbacks/Pingbacks
[...] REVERT TO CONSOLE › Mock Unit Testing Services and Daos with Mockito and Spring 3 – Mock Unit Testing Services and Daos with Mockito and Spring 3 [...]
[...] Mock Unit Testing Services and Daos with Mockito and Spring 3 [...]
Post a Comment