1/02/2013

Mockito Study Notes and Examples

Mockito

Official Website:
http://code.google.com/p/mockito/

Official Website manual book:
http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html

The examples in the manual is very useful and can cover most of the common cases.

After reading and implementing several mock test cases. I feel like using anotation is a very good and clear way to implement Mock testing.

But as in the official manual book, DO NOT forget to initialize the mock anotation. Or all the mock objects will not be created.
There are two ways to initialize the mock anotation.
One way is to use test runner:
@RunWith(MockitoJUnitRunner.class
public class EmailServiceTest {
  
  ... ...
}
The other way is to initialize it in the @Before block:
01 @Before
02 public void init(){
03     MockitoAnnotations.initMocks(this);
04     
05     EnvironmentProperties props = new EnvironmentProperties();
06     List<Map<String, String>> propsList = new ArrayList<Map<String, String>>();
07     Map<String, String> map = new HashMap<String, String>();
08     map.put("mail.smtp.port", "25");
09     map.put("mail.transport.protocol", "smtp");
10     map.put("mail.store.protocol", "pop3");
11     map.put("test.domain.name", "testnetwork.com");
12     propsList.add(map);
13     props.setProperties(propsList);
14     emailService.setEnvironmentProperties(props);
15 }

The basic testing cases are almost covered by the official manual book. Beyond that, if we want to mock the object injected by spring, especially spring 3.0 or higher, using anotation. Now I want to record my study notes and examples.

First, if we use mock object to replace the spring injection. We do not need to add spring runner in our unit test. Even though we use the spring runner, the object that created by spring is different from the mock object that we created in the mock test cases.

Example. Here I have one service class, EmailService.class. Its concrete class is as below:

001 @Service("emailService")
002 @Qualifier("emailService")
003 public class EmailServiceImpl implements EmailService {
004 
005     private static final Logger LOGGER = Logger
006             .getLogger(EmailServiceImpl.class);
007 
008     private String smtpPort;
009     private String MailTransportProtocol;
010     private String mailStoreProtocol;
011     private String directDomain;
012 
013     @Autowired
014     private UserService userService;
015 
016     @Autowired
017     @Qualifier("mailSender")
018     private JavaMailSender mailSender;
019 
020     @Autowired
021     @Qualifier("MailBoxDao")
022     private MailBoxDao mailBoxDao;
023     @Autowired
024     @Qualifier("labelService")
025     private LabelService labelService;
026 
027     @Autowired
028     @Qualifier("MailMessageDao")
029     private MailMessageDao mailMessageDao;
030 
031     @Autowired
032     @Qualifier("EnvironmentProperties")
033     public void setEnvironmentProperties(EnvironmentProperties props{
034         if (props != null{
035             initDefaultFormatsAndLabels(props);
036         } else {
037             LOGGER.warn("Please check the application configuration. Missing mail.properties"
038                     + "from classpath");
039         }
040     }
041 
042     private void initDefaultFormatsAndLabels(EnvironmentProperties props{
043 
044         smtpPort = props.getProperty("mail.smtp.port");
045         MailTransportProtocol = props.getProperty("mail.transport.protocol");
046         mailStoreProtocol = props.getProperty("mail.store.protocol");
047         directDomain = props.getProperty("test.domain.name");
048         LOGGER.info("Server setting loaded.");
049         LOGGER.info("Transport protocol : " + MailTransportProtocol
050                 + " Port : " + smtpPort);
051         LOGGER.info("Mail store protocol : " + mailStoreProtocol);
052         LOGGER.info("Domain : " + directDomain);
053     }
054 
055     @Override
056     public void sendEmail(MailMessageDetails clientMessage{
057 
058         LOGGER.info("Start composing Email.");
059 
060         .....
061     }
062 
063     @Override
064     public void routeMail(MailMessage message{
065 
066         removeInvalidRecipient(message);
067 
068         List<Recipient> recipients = message.getRecipients();
069         if (!CollectionUtils.isEmpty(recipients)) {
070 
071             for (int i = 0i < recipients.size(); i++) {
072 
073                 String directEmail = recipients.get(i).getDirectEmail();
074                 if (StringUtils.isNotEmpty(directEmail)) {
075                     String domain = directEmail.split("@")[1];
076                     if (domain.equalsIgnoreCase(directDomain)) {
077 
078                         String directUserName = directEmail.split("@")[0];
079 
080                         String inboxLabel = getInboxLabelReference(directUserName);
081                         String mailBoxId = findMailboxId(directUserName);
082                         message.setUserName(directUserName);
083                         message.setMailboxId(mailBoxId);
084                         message.setLabelId(inboxLabel);
085 
086                         message.setBccRecipients(null);
087                         message.setId(null);
088 
089                         String id = mailMessageDao.saveMessage(message);
090                         LOGGER.debug("Message saved into " + directUserName
091                                 + "'s inbox. id = " + id);
092                     }
093                 }
094             }
095         }
096 
097         List<Recipient> ccRecipients = message.getCcRecipients();
098         
099         ... ...
100 
101     }
102 
103     public void removeInvalidRecipient(MailMessage message{
104 
105              ......
106         
107     }
108 
109     public String getInboxLabelReference(String userName{
110         return labelService.getLabelReference(userName, Label.INBOX);
111     }
112 
113     public String findMailboxId(String user{
114         Mailbox mailBox = mailBoxDao.findMailbox(user);
115         if (mailBox != null{
116             return mailBox.getId();
117         }
118         return null;
119     }
120 
121 }

Now if I want to mock this service and verify the result of routing message, whether invalid recipients are removed, whether message is saved to the valid recipient's mailbox...etc.

I can construct the object like this:

001 @RunWith(MockitoJUnitRunner.class
002 public class EmailServiceTest {
003 
004     @Mock
005     private UserService userService;
006     @Mock
007     private MailMessageDao mailMessageDao;
008     @Mock
009     private LabelService labelService;
010     @Mock
011     private MailBoxDao mailBoxDao;
012     @InjectMocks
013     private EmailServiceImpl emailService;
014     
015     @Before
016     public void init(){
017         //MockitoAnnotations.initMocks(this);
018         
019         EnvironmentProperties props = new EnvironmentProperties();
020         List<Map<String, String>> propsList = new ArrayList<Map<String, String>>();
021         Map<String, String> map = new HashMap<String, String>();
022         map.put("mail.smtp.port", "25");
023         map.put("mail.transport.protocol", "smtp");
024         map.put("mail.store.protocol", "pop3");
025         map.put("test.domain.name", "testnetwork.com");
026         propsList.add(map);
027         props.setProperties(propsList);
028         emailService.setEnvironmentProperties(props);
029     }
030     @Test
031     public void routeDirectMailTest(){
032 
033         User user = new User();
034         user.setUserName("marym");
035         user.setDirectUserName("marym");
036         List<User> userList = new ArrayList<User>();
037         userList.add(user);
038         
039         MailMessage message = new MailMessage();
040         Recipient sender = new Recipient();
041         sender.setDirectEmail("alice@test.com");
042         Recipient recipient = new Recipient();
043         recipient.setDirectEmail("marym@testnetwork.com");
044         List<Recipient> recipients = new ArrayList<Recipient>();
045         recipients.add(recipient);
046         message.setSender(sender);
047         message.setRecipients(recipients);
048         
049         Mailbox mailBox = new Mailbox();
050         mailBox.setId("argrwgarwgrarfarfw");
051         
052         Mockito.when(userService.findByFilter(Mockito.argThat(new IsValidUser()))).thenReturn(userList);
053         Mockito.when(mailMessageDao.saveMessage(message)).thenReturn("abcderfhighaeraerare");
054         Mockito.when(labelService.getLabelReference(Mockito.eq(Mockito.anyString()),Label.INBOX)).thenReturn("abcdafhduahufhkahfdj");
055         Mockito.when(mailBoxDao.findMailbox(Mockito.anyString())).thenReturn(mailBox);
056 
057         directEmailService.routeDirectMail(message);
058         
059         verify(userService,Mockito.times(1)).findByFilter(Mockito.argThat(new IsValidUser()));
060         verify(mailMessageDao,Mockito.times(1)).saveMessage(message);
061         
062     }
063     
064     @Test
065     public void routeDirectMailWithInvalidRecipientTest(){
066 
067         User user = new User();
068         user.setUserName("marym");
069         user.setDirectUserName("marym");
070         List<User> userList = new ArrayList<User>();
071         userList.add(user);
072         
073         MailMessage message = new MailMessage();
074         Recipient sender = new Recipient();
075         sender.setDirectEmail("alice@test.com");
076         
077         List<Recipient> recipients = new ArrayList<Recipient>();
078         
079         Recipient recipient = new Recipient();
080         recipient.setDirectEmail("marym@testnetwork.com");
081         recipients.add(recipient);
082         
083         recipient = new Recipient();
084         recipient.setDirectEmail("marym.com");
085         recipients.add(recipient);
086         
087         message.setSender(sender);
088         message.setRecipients(recipients);
089         
090         Mailbox mailBox = new Mailbox();
091         mailBox.setId("argrwgarwgrarfarfw");
092         
093         Mockito.when(userService.findByFilter(Mockito.argThat(new IsValidUser()))).thenReturn(userList);
094         Mockito.when(mailMessageDao.saveMessage(message)).thenReturn("abcderfhighaeraerare");
095         Mockito.when(labelService.getLabelReference(Mockito.eq(Mockito.anyString()),Label.INBOX)).thenReturn("abcdafhduahufhkahfdj");
096         Mockito.when(mailBoxDao.findMailbox(Mockito.anyString())).thenReturn(mailBox);
097         
098         directEmailService.routeDirectMail(message);
099         
100         verify(userService,Mockito.times(1)).findByFilter(Mockito.argThat(new IsValidUser()));
101         verify(mailMessageDao,Mockito.times(1)).saveMessage(message);
102     }



I use @Mock to mock the other services and dao that used in my service and use @InjectMocks to mock my service.

And what needs to be verify is that the @InjectMocks can only mock concrete class, cannot mock interface.

Because I do not user spring injection now, so I have to set the environmentProperty myself in the test cases.
Or the properties will all left null.

The first test case is that the recipient is valid and the message should be saved in the recipient's mail box. So,   verify(mailMessageDao,Mockito.times(1)).saveMessage(message); 
The times is 1 means the method saveMessage() with input message should only be executed for 1 time.

The second test case is that there're two recipients, but one recipient is not valid and should be removed from the recipients' list.
So,  verify(mailMessageDao,Mockito.times(1)).saveMessage(message); 

Easy and interesting.

Actually I have seen other developer's test case and they also mock like this:

01 @RunWith(SpringJUnit4ClassRunner.class)
02 @ContextConfiguration("classpath*:spring/*-context.xml")
03 public class SecurityServiceTest extends TestCase {
04     @Autowired
05     private SecurityServiceImpl securityService;
06     @Test
07     public void testResetPassword() {
08         String email = "not-found@localhost";
09         MailingService mailingSvc = mock(MailingService.class);
10         securityService.setMailingService(mailingSvc);
11  
12         boolean isSent = securityService.resetPassword(email);
13         Assert.assertFalse(isSent);
14  
15         verify(mailingSvc, times(0)).sendMail(
16                 Matchers.any(MailType.class),
17                 (java.util.Map<String, Object>) Matchers.any(Map.class), Matchers.eq(new String[] {email}));
18  
19         email = "test@localhost";
20         isSent = securityService.resetPassword(email);
21         Assert.assertTrue(isSent);
22         verify(mailingSvc, times(1)).sendMail(Matchers.any(MailType.class),
23                 (java.util.Map<String, Object>) Matchers.any(Map.class), Matchers.eq(new String[] {email}));
24     }
25 }

They use spring runner instead of mock runner.  And they do not mock their service, but use spring container to create the concrete object for their service.

This way can work because they add setters for elements in their service.
In the test case, they can create the mock object in the service and set the mock object to the object that created by spring.

Also workable, but I do not like this way. Because when autowire, you have to autowire the serviceImpl instead of interface, and the service impl needs to have set method.

Keep studying!!!

No comments:

Post a Comment