spring and Mockito

来源:互联网 发布:员工监控软件 编辑:程序博客网 时间:2024/06/05 05:35


Mockito has become a very popular and well-documented open source mock testing library. Spring is obviously the application framework of choice these days. Most of the time when writing unit tests for your Service layer, you will either be writing a Mockito test or a Spring test. Mockito tests are used to isolate testing to only the method being tested. Mock objects stand-in for and simulate the behavior of real objects and thus allow the tester to control the behavior of any dependent objects.

On the other hand, Spring tests are commonly used when you want to verify that Spring dependency injection is working and wire-up certain classes that have been configured for testing. A common usage of a Spring test is for testing data access objects (DAO) that access an in-memory database for testing instead of the actual database.  

Here is an example JUnit4 test case that uses Mockito as the mocking framework:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@RunWith(MockitoJUnitRunner.class)
publicclass AccountServiceTest {
 
    @Mock
    privateNotificationService notificationService;
 
    @Mock
    privateAccountDAO accountDAO;
 
    @InjectMocks
    privateAccountServiceImpl accountService = newAccountServiceImpl();
 
    @Test
    publicvoid createNewAccount() {
 
        // Expected objects
        String name = "Test Account";
        Account accountToSave = newAccount(name);
        longaccountId = 12345;
        Account persistedAccount = newAccount(name);
        persistedAccount.setId(accountId);
 
        // Mockito expectations                           
        when(accountDAO.save(any(Account.class))).thenReturn(persistedAccount);
        doNothing().when(notificationService).notifyOfNewAccount(accountId);
 
        // Execute the method being tested    
        Account newAccount = accountService.createNewAccount(name);
 
        // Validation 
        assertNotNull(newAccount);
        assertEquals(accountId, newAccount.getId());
        assertEquals(name, newAccount.getName());
 
        verify(notificationService).notifyOfNewAccount(accountId);
        verify(accountDAO).save(accountToSave);
    }
}

You’ll notice the use of annotations throughout the test. The @RunWith annotation causes the MockitoJunitRunner to execute the test. This runner extends a JUnit runner and takes care of creating any mocks and setting them on the class under test. In the test above, the NotificationService and AccountDAO are mocked and injected into the AccountServiceImpl.

Below is an example Spring test case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:test-context.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
publicclass AccountDAOTest {
 
    @Autowired
    privateAccountDAO accountDAO;
     
    @PersistenceContext
    privateEntityManager entityManager;
     
    @Test
    @Transactional
    publicvoid save() {  
     
         Account newAccount = newAccount("Test Account");
 
         // Execute the method being tested
         accountDAO.save(newAccount);
          
         // Validation
         assertNotNull(newAccount.getId());
         Account reloaded = flushAndGet(newAccount.getId());
         assertEquals(newAccount.getId(), reloaded.getId());
         assertEquals(newAccount.getName(), reloaded.getName());
    }
     
    privateAccount flushAndGet(longid) {
         entityManager.flush();
         returnentityManager.find(Account.class, id);
    }
}

In this example Spring test case, the SpringJUnit4ClassRunner initializes the Spring application framework and configures and loads the objects in the test-context.xml file. This configuration initializes and configures an in-memory database (such as the H2 Database Engine). The AccountDAO is autowired into the test and its save() method is tested. By using a Spring test instead of a Mockito test, the actual Hibernate mappings and underlying database queries can be tested.

Sometimes, however, you may want to be able to mock a dependency or set of dependencies and also use Spring to inject the remaining dependencies. This is a scenario that is probably not real common and typically not the best practice for most tests since it introduces additional complexity into the test case but there are some legitimate reasons for wanting to set up a test this way. Some scenarios where you may want to use both Mockito and Spring are as follows:

  • You are testing a complex method that has dependencies on both a service that accesses the database and a service that makes a remote procedure call to another server. You want to use Mockito to mock the service that makes the remote procedure call but you want to use Spring to wire all the other dependencies.
  • You are testing exception handling and it’s difficult to reproduce a specific exception from the real object. It’s easy to test the exception handling behavior of an object using Mockito because you can mock an object and declare that a method throws a specific exception whenever it’s called. You may want to autowire all other dependencies using Spring.
  • A system may be under development, and the interfaces but not the implementations may exist for a number of dependencies. You may want to autowire all other (implemented) dependencies using Spring but mock the unimplemented implementations.
  • You may want to mock a service that is slow to execute in order to speed up test runs but autowire all other dependencies using Spring.

Continuing on with the AccountService and AccountDAO examples above, here is a hypothetical example: Let’s say that we want to test the delete() method on AccountService. An account can only be deleted if the logged in user has the correct permissions. Spring Security is used to implement permissions, so we want to use a Spring test so that we can verify that the Spring Security configuration is correct.

However, if an account is deleted, then as part of the service layer logic a REST call is made to another system for auditing purposes. That third party system is difficult to set up and run within a unit test. Besides, the third party system shouldn’t be set up anyway because this is a unit test and we just want to test the behavior of the AccountService delete() method and not the third party system. In other words, it’s good enough to test that the notify() method is called – perfect for a mock.

But how can we do this, use both Spring’s testing framework and Mockito? We can use a MockitoJUnitRunner or a SpringJUnit4ClassRunner, but not both. The first time I ran across a case like this I was stumped. I went down the path of creating my own mock and setting this mock up as a Spring bean in the test application context so that it could be autowired into my Spring test. It turns out that there is a much easier way.

MockitoJUnitRunner calls MockitoAnnotations.initMocks() to do its setup work. So if we just use the initMocks() method instead of the MockitoJUnitRunner and keep SpringJUnit4ClassRunner, it all works!  Here’s some example code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:test-context.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
publicclass AccountServiceSpringAndMockitoTest {
 
    @Mock
    privateAuditService mockAuditService;
 
    @InjectMocks
    @Autowired
    privateAccountService accountService;
 
    @Before
    publicvoid setup() {
        MockitoAnnotations.initMocks(this);
    }
 
    @Test
    @Transactional
    publicvoid deleteWithPermission() {
 
        // Set up Spring Security token for testing
        SecuredUser user = newSecuredUser();
        user.setUsername("test1");
        TestingAuthenticationToken token = newTestingAuthenticationToken(user, null,"accountFullAccess");
        SecurityContextHolder.getContext().setAuthentication(token);
 
        // Create account to be deleted
        Account accountToBeDeleted = accountService.createNewAccount("Test Account");
        longaccountId = accountToBeDeleted.getId();
         
        // Mockito expectations
        doNothing().when(mockAuditService).notifyDelete(accountId);
 
        // Execute the method being tested
        accountService.delete(accountToBeDeleted);
 
        // Validation
        assertNull(accountService.get(accountId));
        verify(mockAuditService).notifyDelete(accountId);
    }
}

Notice the setup() method that is run before each test. In it, MockitoAnnotations.initMocks() is called. By the time initMocks() is called, Spring has already injected all of the other dependencies. The AccountService implementation is injected by Spring and then the mockAuditService is set on this implementation by Mockito.  Pretty cool!

I am hopeful that this article describes not only how to use Mockito and Spring together, but also provides some good examples about why or when you would want to use them together in a unit test. The references section below provides a couple of links that give some other good examples of using Mockito and Spring together. Happy testing!

References

  • http://stackoverflow.com/questions/10906945/mockito-junit-and-spring
  • http://markchensblog.blogspot.com/2013/02/use-spring-mvc-test-framework-and.html
  • https://en.wikipedia.org/wiki/Mock_object

The full source code used for this article can be found on our git repository.


0 0
原创粉丝点击