SpringBoot单元测试
针对不同的代码层次, 有不同的测试注意事项.
我的理解是:
针对Mapper层或者是Dao层,由于需要跟数据库交互, 写完代码的时候手工执行就好, 由于数据库的数据的不稳定性,不适合用作大量回归测试.
针对Service层的单元测试才是我们通常理解的那种单元测试或者说白盒测试.
针对Controller层的测试,如果Mock了Service层的行为,那这个算是白盒测试,如果没有Mock Service层行为,而是直接调用,我觉得其实已经可以算是集成测试了,这个偏向黑盒测试的范畴,就是我们通常说的API测试.
1. 针对Mapper层或者Dao层
如果是有依赖其他Bean对象,可以先写一个专门用来装配Bean的类,并用注解TestConfiguration标注.
当然,这一步也不是必须的,这个只是为了方便在单元测试类中使用@Autowired引入对象.可以在单元测试类中,使用Product product = new Product()代替.
1 import org.springframework.boot.test.context.TestConfiguration; 2 import org.springframework.context.annotation.Bean; 3 4 import com.windy.mall.product.bean.Product; 5 6 @TestConfiguration 7 public class TestBeanConfiguration { 8 9 @Bean 10 public Product createProduct(){ 11 return new Product(); 12 } 13 14 }
单元测试类中必须要声明:
(1) @RunWith(Spring.class),表示要在Spring环境中做测试, 于是就可以使用@Autowired等注解了,
(2) @SpringBootTest(classes=TestBeanConfiguration.class),表示可以使用SpringBoot环境了,这样可以用@Autowired注解,把@Mapper注释过的Mapper对象引入.为了引入外部依赖的Bean对象,需要指定classes=TestBeanConfiguration.class.
注意: 之所以要指定classes=TestBeanConfiguration.class,这时因为Product这个类,并没有使用@Component等注解注入IOC容器.
可选:
(3) @Transactional注解来开启事务
(4) @Rollback(true)表示开启回滚数据功能,默认为True.
1 import org.junit.Assert; 2 import org.junit.Before; 3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.boot.test.context.SpringBootTest; 7 import org.springframework.test.annotation.Rollback; 8 import org.springframework.test.context.junit4.SpringRunner; 9 import org.springframework.transaction.annotation.Transactional; 10 11 import com.windy.mall.product.bean.Product; 12 import com.windy.mall.product.bean.TestBeanConfiguration; 13 14 @RunWith(SpringRunner.class) 15 @SpringBootTest(classes=TestBeanConfiguration.class) 16 @Transactional 17 @Rollback(true) 18 public class ProductMapperTest { 19 20 @Autowired 21 private ProductMapper mapper; 22 23 @Autowired 24 private Product product; 25 26 @Before 27 public void setUp(){ 28 product.setPname("持续交付"); 29 product.setType("书籍"); 30 product.setPrice(69d); 31 } 32 33 @Test 34 public void testAdd() { 35 Assert.assertEquals(Integer.valueOf(1), mapper.add(product)); 36 } 37 38 }
2. 针对Service层
由于Service层需要调用Mapper层或者是Dao层,通常需要Mock Mapper对象.这里需要使用到@MockBean注解,Mock Mapper层的行为.
1 package com.windy.mall.product.service; 2 3 import org.junit.Assert; 4 import org.junit.Before; 5 import org.junit.Test; 6 import org.junit.runner.RunWith; 7 import org.mockito.BDDMockito; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.boot.test.context.SpringBootTest; 10 import org.springframework.boot.test.mock.mockito.MockBean; 11 import org.springframework.test.context.junit4.SpringRunner; 12 13 import com.windy.mall.product.bean.Product; 14 import com.windy.mall.product.bean.TestBeanConfiguration; 15 import com.windy.mall.product.mapper.ProductMapper; 16 17 @RunWith(SpringRunner.class) 18 @SpringBootTest(classes = TestBeanConfiguration.class) 19 public class ProductServiceTest { 20 21 @MockBean 22 private ProductMapper mapper; 23 24 @Autowired 25 private Product product; 26 27 @Autowired 28 private ProductService service; 29 30 @Before 31 public void setUp() { 32 BDDMockito.given(mapper.add(product)).willReturn(1); 33 BDDMockito.given(mapper.add(null)).willReturn(0); 34 } 35 36 @Test 37 public void testAddService() { 38 Assert.assertEquals(Integer.valueOf(1), service.add(product)); 39 Assert.assertEquals(Integer.valueOf(0), service.add(null)); 40 } 41 42 }
3.针对Controller
有两种方法可以做单元测试:
(1) 这里需要用到web环境(webEnvironment=WebEnvironment.RANDOM_PORT).需要用到TestRestTemplate这个对象作为客户方,调用Controller(API)
1 import org.junit.Assert; 2 import org.junit.Test; 3 import org.junit.runner.RunWith; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.boot.test.context.SpringBootTest; 6 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 7 import org.springframework.boot.test.web.client.TestRestTemplate; 8 import org.springframework.test.context.junit4.SpringRunner; 9 10 @RunWith(SpringRunner.class) 11 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 12 public class ProductControllerTest { 13 14 @Autowired 15 private TestRestTemplate template; 16 17 @Test 18 public void test() { 19 String body = template.getForObject("/ms/product/1", String.class); 20 Assert.assertEquals("success", body); 21 } 22 23 }
(2) 如果是使用MockMVC对象来测试,需要开启@AutoConfigureMockMvc注解
1 import org.junit.Test; 2 import org.junit.runner.RunWith; 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 5 import org.springframework.boot.test.context.SpringBootTest; 6 import org.springframework.test.context.junit4.SpringRunner; 7 import org.springframework.test.web.servlet.MockMvc; 8 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 9 import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 10 11 @RunWith(SpringRunner.class) 12 @SpringBootTest 13 @AutoConfigureMockMvc 14 public class ProductControllerTest2 { 15 16 @Autowired 17 private MockMvc mvc; 18 19 @Test 20 public void test() throws Exception { 21 mvc.perform(MockMvcRequestBuilders.get("/ms/product/1")) 22 .andExpect(MockMvcResultMatchers.status().isOk()) 23 .andExpect(MockMvcResultMatchers.content().string("success")); 24 } 25 26 }