Mar 6, 2012

Injecting Mockitto mocks using Spring @Autowired


We are using @Autowired (by Type) for dependency management of our multi-layer/multi-tiered Spring 3 project. Here is how our interface/implementation look,

public interface UserProfileService {

       public abstract UserProfileDO getUser(String userId) throws UserNotFoundException;

}

@Service
public class UserProfileServiceImpl extends BaseService implements UserProfileService, InitializingBean
{

       @Autowired
       private UserProfileDAO userprofileDao;

       @Override
       public void afterPropertiesSet() throws Exception
       {
              Assert.notNull(userprofileDao, "UserProfileDAO not injected.");
       } 
      
       @Override
       public UserProfileDO getUser(String userId) throws UserNotFoundException
       {
              IMPersonEntity person;
              try
              {
                     person = userprofileDao.getUser(userId);
              }
              catch (DataAccessException e)
              {
                     throw new UserNotFoundException(userId);
              }
              if (person == null)
                     throw new UserNotFoundException(userId);

              UserProfileDO userProfile = (UserProfileDO) objectConversion(person, UserProfileDO.class);
              return userProfile;
       }

       public void setUserprofileDao(UserProfileDAO userprofileDao)
       {
              this.userprofileDao = userprofileDao;
       }

}

UserProfileService has two dependent beans
·         UserProfileDAO: Needs to be  mocked. This is a data-access layer which is implementated using  IBMs Tivoli APIs and LDAP.
·         dozerObjectMapper: This dependency is from the BaseService, which converts the Entities into DomainObject. We don’t intend to mock this.

To configure Mockitto, I  started by using @Mock annotation but in my test I needed a mix of @Autowired and @Mock. I  don’t think MockitoJUnit44Runner loads the beans from ContextConfiguration. Also, I didn’t find any special value on using @Mock annotation (This is my first mockitto test, maybe I will realize its value later).

@RunWith(MockitoJUnit44Runner.class)
@ContextConfiguration("classpath:mock-userprofile-service-context.xml")
public class UserProfileServiceTest implements InitializingBean
{
       @Autowired    private UserProfileService userDetailsService;
       @Mock         private UserProfileDAO mockUserDetailsDao;

Other issue with my test was that my ServiceInterface (UserProfileService) did not have setter for DAO ( as per desing), that means I cannot set my mockObject on the service. I overcame this problem by using (abusing) Reflection.
So, I changed my test to switched back to SpringJUnit4ClassRunner (removed @Mock annotation) and I used reflection to set  mockObject to the testService.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:mock-userprofile-service-context.xml")
public class UserProfileServiceTest implements InitializingBean
{
       @Autowired    
private UserProfileService userDetailsService;
       private UserProfileDAO mockUserDetailsDao = mock(UserProfileDAO);

@Test
       public void getValidUser() throws UserNotFoundException, EntityNotFoundException, MultipleEntitiesFoundException
       {
              IMPerson mockPerson = new IMPerson();
              mockPerson.setDisplayName("test cca");
              when(mockUserDetailsDao.getUser("cca_test_user")).thenReturn(mockPerson);
ReflectionTestUtils.setField(userDetailsService, "userProfileDAO", mockUserDetailsDao);
              UserProfileDO user = userDetailsService.getUser("cca_test_user");
              assertNotNull(user);
              Assert.assertEquals("test cca", user.getDisplayName());

       }

This got me going, I could run tests with mock objects. I still wanted to get rid of reflection and find a way to Autowire mockObject (maintain consistency across application).
So,  I moved my mockbean creation to  test context and had @Autowired in service detect the required mock Dao (no more Reflection). I still needed a reference to mock bean in my test, to configure mock conditions.
This is how my final test context looks, with mock bean creation.

mock-userprofile-service-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">


<bean  id="mockUserProfileDao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.company.dataaccess.userprofile.dao.UserProfileDAO" />
</bean>

<bean class="com.company.service.userprofile.UserProfileServiceImpl"/>
<bean class="org.dozer.DozerBeanMapper"/>

</beans> 
This is how my  final test looks

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:mock-userprofile-service-context.xml")
public class UserProfileServiceTest
{ 
       @Autowired
       private UserProfileService userDetailsService;
       @Autowired
       private UserProfileDAO mockUserDetailsDao;
      

       @Test
       public void getValidUser() throws UserNotFoundException, EntityNotFoundException, MultipleEntitiesFoundException
       {
              IMPerson mockPerson = new IMPerson();
              mockPerson.setDisplayName("test cca");
              when(mockUserDetailsDao.getUser("cca_test_user")).thenReturn(mockPerson);
              UserProfileDO user = userDetailsService.getUser("cca_test_user");
              assertNotNull(user);
              Assert.assertEquals("test cca", user.getDisplayName());

       }
       @Test
       @ExpectedException(UserNotFoundException.class)
       public void getMultipleInValidUser() throws UserNotFoundException, EntityNotFoundException, MultipleEntitiesFoundException
       {
              when(mockUserDetailsDao.getUser("multipleUserids")).thenThrow(new MultipleEntitiesFoundException());
              userDetailsService.getUser("multipleUserids");
              fail();

       }
       @Test
       @ExpectedException(UserNotFoundException.class)
       public void getInValidUser() throws UserNotFoundException, EntityNotFoundException, MultipleEntitiesFoundException
       {
              when(mockUserDetailsDao.getUser("nulluser")).thenReturn(null);
              userDetailsService.getUser("nulluser");
              fail();

       }
      
}

Happy testing J