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:
1 @RunWith(MockitoJUnitRunner.class)
2 public class EmailServiceTest {
3
4 ... ...
5 }
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 = 0; i < 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.
The second test case is that there're two recipients, but one recipient is not valid and should be removed from the recipients' list.
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);
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!!!
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 }
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