最近用Spring Mvc框架做了几个小项目,但都没有做单元测试,最近想恶补一下这方面的东西,包括基于Spring的单元测试,自动化测试和JS单元测试。今天先讲一下基于Spring框架的单元测试,测试使用的是Spring自带的test组件,再结合Mockito一起编写测试案例,以下示例会包括Controller和Service,由于Repository是基于Spring JPA,没有自己的逻辑,所以这里就不涉及Repository的单元测试,以后有需要再介绍。
Controller
首先看一下Controller的代码(如下),代码比较简单,就是接收前端发过来的一些参数,通过这些参数直接调用Service的方法。
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
| import com.odde.mail.model.Result; import com.odde.mail.service.MailService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody;
import static java.lang.String.format;
@Controller @RequestMapping("/mail") public class MailController { private static final Log log = LogFactory.getLog(MailController.class); private final ObjectMapper mapper = new ObjectMapper();
@Autowired private MailService mailService;
@RequestMapping(value = "/send", method = RequestMethod.POST, produces = "text/plain;charset=UTF-8") public @ResponseBody String send(@RequestParam("recipients") String recipients, @RequestParam("subject") String subject, @RequestParam("content") String content) throws Exception { log.debug("mail controller send start"); log.debug(format("recipients:%s", recipients)); log.debug(format("subject:%s", subject)); log.debug(format("content:%s", content)); Result mailResult = mailService.send(recipients, subject, content); String result = mapper.writeValueAsString(mailResult); log.debug(format("result:%s", result)); log.debug("mail controller send finish"); return result; } }
|
再来看对应的单元测试:
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 44 45 46 47 48 49 50 51 52 53
| import com.odde.mail.model.Result; import com.odde.mail.service.MailService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.hamcrest.CoreMatchers.is; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml") public class MailControllerTest { private MockMvc mockMvc;
@Mock private MailService mailService;
@InjectMocks MailController mailController;
@Before public void setup() { MockitoAnnotations.initMocks(this); this.mockMvc = MockMvcBuilders.standaloneSetup(mailController).build(); }
@Test public void should_return_status_success_when_send_mail_success() throws Exception { when(mailService.send("test@test.com", "test", "test")).thenReturn(new Result("成功"));
mockMvc.perform(post("/mail/send") .param("recipients", "test@test.com") .param("subject", "test") .param("content", "test")) .andDo(print()) .andExpect(status().isOk()).andExpect(content().string(is("{\"status\":\"" + result + "\"}")));
verify(mailService).send("test@test.com", "test", "test"); }
|
首先是Spring的几个Annotate
- RunWith(SpringJUnit4ClassRunner.class): 表示使用Spring Test组件进行单元测试;
- WebAppConfiguration: 使用这个Annotate会在跑单元测试的时候真实的启一个web服务,然后开始调用Controller的Rest API,待单元测试跑完之后再将web服务停掉;
- ContextConfiguration: 指定Bean的配置文件信息,可以有多种方式,这个例子使用的是文件路径形式,如果有多个配置文件,可以将括号中的信息配置为一个字符串数组来表示;
然后是Mockito的Annotate
- Mock: 如果该对象需要mock,则加上此Annotate;
- InjectMocks: 使mock对象的使用类可以注入mock对象,在上面这个例子中,mock对象是MailService,使用了MailService的是MailController,所以在Controller加上该Annotate;
Setup方法
MockitoAnnotations.initMocks(this)
: 将打上Mockito标签的对象起作用,使得Mock的类被Mock,使用了Mock对象的类自动与Mock对象关联。
mockMvc
: 细心的朋友应该注意到了这个对象,这个对象是Controller单元测试的关键,它的初始化也是在setup方法里面。
Test Case
- 首先mock了MailService的send方法,让其返回一个成功的Result对象。
mockMvc.perform
: 发起一个http请求。
post(url)
: 表示一个post请求,url对应的是Controller中被测方法的Rest url。
param(key, value)
: 表示一个request parameter,方法参数是key和value。
andDo(print())
: 表示打印出request和response的详细信息,便于调试。
andExpect(status().isOk())
: 表示期望返回的Response Status是200。
andExpect(content().string(is(expectstring))
: 表示期望返回的Response Body内容是期望的字符串。
使用print打印处理的信息类似下面显示的内容:
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
| MockHttpServletRequest: HTTP Method = POST Request URI = /mail/send Parameters = {recipients=[test@test.com], subject=[test], content=[test]} Headers = {}
Handler: Type = com.odde.mail.controller.MailController Method = public java.lang.String com.odde.mail.controller.MailController.send(java.lang.String,java.lang.String,java.lang.String) throws java.lang.Exception
Async: Was async started = false Async result = null
Resolved Exception: Type = null
ModelAndView: View name = null View = null Model = null
FlashMap:
MockHttpServletResponse: Status = 200 Error message = null Headers = {Content-Type=[text/plain;charset=UTF-8], Content-Length=[19]} Content type = text/plain;charset=UTF-8 Body = {"status":"成功"} Forwarded URL = null Redirected URL = null Cookies = []
|
Service
照例我们先看一下Service的功能代码,代码也比较简单,就是调用Repository做一些增删改查的动作。
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
| import com.odde.mail.model.Recipient; import com.odde.mail.model.Result; import com.odde.mail.repo.RecipientRepository; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
import java.util.List;
@Service public class RecipientService {
@Autowired private RecipientRepository recipientRepository;
public Result add(String username, String email) { Recipient recipient = recipientRepository.findByEmail(email); Result result; if (recipient == null) { recipientRepository.save(new Recipient(username, email)); result = new Result("成功"); } else { result = new Result("失败"); } return result; } }
|
再来看对应的测试代码:
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 44 45
| import com.odde.mail.model.Recipient; import com.odde.mail.repo.RecipientRepository; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
import static java.util.Arrays.asList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when;
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml") public class RecipientServiceTest {
@Mock private RecipientRepository recipientRepository;
@InjectMocks private RecipientService recipientService;
@Before public void setup() { MockitoAnnotations.initMocks(this); }
@Test public void should_return_success_when_add_recipient_not_exist() throws Exception { when(recipientRepository.findByEmail(anyString())).thenReturn(null); when(recipientRepository.save(any(Recipient.class))).thenReturn(null);
assertThat(recipientService.add("Tom", "test@test.com").getStatus(), is("成功")); verify(recipientRepository).findByEmail(anyString()); verify(recipientRepository).save(any(Recipient.class)); }
|
Service的单元测试就比较简单了,大部分内容都在Controller里面讲过,不同的地方就是Controller是使用mockMvc对象来模拟Controler的被测方法,而在Service的单元测试中则是直接调用Service的方法(比如上面例子中的findByEmail和add)。
Reponsitory
最后再说一下Reponsitory的单元测试,刚才讲过这里不涉及这块的介绍,因为Reponsitory没有具体的实现代码,基本上调用的是Spring JPA的功能。
1 2 3 4 5 6 7
| import com.odde.mail.model.Recipient; import org.springframework.data.jpa.repository.JpaRepository;
public interface RecipientRepository extends JpaRepository<Recipient, Long> {
public Recipient findByEmail(String email); }
|
如果你的项目里面有自定义的Reponsitory具体实现,则需要做单元测试,这个可以上网自行搜索相关资料。