@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。
Junit & TestNG
Spring Boot Test 各个场景 —— 有点过时
JUnit 5 Test - MockMvc, Mockito, JPA - 韩语
@SpringbootTest注解
启动Springboot,准备DI等,可以用@Autowire这些注解。如果不加@SpringBootTest,就是普通的类没有DI
Mockito
只看4.1-4.4,spy和mock的区别参见其它资料和项目代码
- 给InjectMocks的类加@Spy注解,然后doReturn
-
@InjectMocks
@mock和@mockBean
[x] @Mock vs. @MockBean When Testing Spring Boot Applications
- @mock是org.mockito包的,@mockBean是springBoot提供的
- @Mock: 与框架无关,不是创建Spring Context,而是通过Mockito实例化对象进行单元测试。We can achieve this mocking behavior using @Mock whether we use Spring Boot or any other framework like Jakarta EE, Quarkus, Micronaut, Helidon, etc.
- 集成测试@SpringBootTest或@WebMvcTest or @DataJpaTest,会创建一个所谓的Spring Test Context,This context is similar to the application context during runtime as we can request beans from it (@Autowired). Usually, it only contains a subset of our beans (making our tests faster). As we can customize the Spring Test Context to our needs, we can decide whether we want the actual or a mocked version of one of our beans inside the context(通过@Autowire真实地注入其它bean,或者@MockBean以Mock的方式注入其它bean)。
- Advice: @mockBean会不断创建applicationContext(打破原来的缓存),降低测试速度。
@MockBean 危害
或者在不能直接mock依赖的时候用@Mockbean,比如context.getBean这种。但是这种用法不好。
@Spy和@SpyBean
@MockBean 不同的是,@SpyBean不会生成一个 Bean 的替代品装配到类中,而是会监听一个真正的 Bean 中某些特定的方法(用doReturn),并在调用这些方法时给出指定的反馈。却不会影响这个 Bean 其它的功能。
reset
单元测试,每个方法里的when..return互不影响;
集成测试,@MockBean是单例的,贯穿于每个method,所以需要在beforeEach中reset
Can I “reset” a mock?
Recently we decided to go on with this feature for tricky scenarios where mocks are created by the container (see issue 55). Before that, the lack of a reset method was deliberate to promote good testing habits and to make the API simpler. Instead of reset() please consider writing simple, small and focused test methods over lengthy, over-specified tests. The discussion about this feature was here.
ArgumentMatchers和ArgumentCaptor
- Using Mockito ArgumentCaptor
- 在Mockito.when()的地方,用ArgumentMatchers,不用ArgumentCaptor。这样就可以直接通过ArgumentMatchers对参数进行了verify ```java Credentials credentials = new Credentials(“baeldung”, “correct_password”, “correct_key”); // eq已经进行了verify Mockito.when(platform.authenticate(Mockito.eq(credentials))) .thenReturn(AuthenticationStatus.AUTHENTICATED);
assertTrue(emailService.authenticatedSuccessfully(credentials));
- 对没有进行stubbing的内部方法调用,使用ArgumentCaptor,再用verify语句验证
```java
Credentials credentials = new Credentials("baeldung", "correct_password", "correct_key");
Mockito.when(platform.authenticate(credentialsCaptor.capture()))
.thenReturn(AuthenticationStatus.AUTHENTICATED);
assertTrue(emailService.authenticatedSuccessfully(credentials));
// 这里还有多写一个
assertEquals(credentials, credentialsCaptor.getValue());
MockitoExtension
// Junit5的较高版本使用
// @ExtendWith(MockitoExtension.class)
class ShipMethodMapperTest {
@InjectMocks
private ShipMethodMapperImpl shipMethodMapper;
// Junit5的较低版本使用
@BeforeEach
void init() {
MockitoAnnotations.initMocks(this);
}
}
mock static静态方法
- 通常不要写静态方法,不方便单元测试。如何使用Mockito模拟Spring ApplicationContext的getBean方法,以便使用TestNG编写单元测试?
- mockito-mock-static-methods Mockito3.4+ ```java import static org.mockito.Mockito.; import static org.junit.Assert.;
@Test public void test1() { // 创建 mock MyClass test = Mockito.mock(MyClass.class);
// 自定义 getUniqueId() 的返回值
when(test.getUniqueId()).thenReturn(43);
// 在测试中使用mock对象
assertEquals(test.getUniqueId(), 43);
}
// 返回多个值 @Test public void testMoreThanOneReturnValue() { Iterator i= mock(Iterator.class); when(i.next()).thenReturn(“Mockito”).thenReturn(“rocks”); String result=i.next()+” “+i.next(); // 断言 assertEquals(“Mockito rocks”, result); }
// 如何根据输入来返回值 @Test public void testReturnValueDependentOnMethodParameter() { Comparable c= mock(Comparable.class); when(c.compareTo(“Mockito”)).thenReturn(1); when(c.compareTo(“Eclipse”)).thenReturn(2); // 断言 assertEquals(1,c.compareTo(“Mockito”)); }
// 如何让返回值不依赖于输入 @Test public void testReturnValueInDependentOnMethodParameter() { Comparable c= mock(Comparable.class); when(c.compareTo(anyInt())).thenReturn(-1); // 断言 assertEquals(-1 ,c.compareTo(9)); }
// 根据参数类型来返回值 @Test public void testReturnValueInDependentOnMethodParameter() { Comparable c= mock(Comparable.class); when(c.compareTo(isA(Todo.class))).thenReturn(0); // 断言 Todo todo = new Todo(5); assertEquals(todo ,c.compareTo(new Todo(1))); }
<a name="R6gij"></a>
## Mock interface和非public类
1. @Mock可以mock interface,@InjectMocks不能mock interface,只能mock实现类
1. 不能@mock非public类,包括包可见类。解决方式:包可见类实现interface,@mock interface
<a name="rmTXv"></a>
## Mock final
1. final依赖类用@Mock,可能只作用于第一个method **TODO:待确认**
```java
public class SampleImpl implements Sample {
// 不能加final
private Sub sub;
// private final Sub sub;
public SampleImpl(Sub sub) {
this.sub = sub;
}
}
public class SampleTest {
@InjectMocks
private Sample sample;
@Mock
private Sub sub;
}
PowerMock
- Introduction to PowerMock
- Using PowerMockito to Mock Final and Static Methods in Java Unit Testing 看到Mock Object Construction (new) with PowerMockito,后面应该不是重点。
The test class needs to extend the “PowerMockTestCase” class. According to the PowerMockito documentations, extending the “PowerMockTestCase” class is just one of the options to make the test class to work, but it also mentioned that extending the “PowerMockTestCase” class is the “safe” option.
the scope of the final and static mocks is limited to the test method where they are specified. the scope of the mocks created by regular Mockito goes beyond the limit of the test method where the mock is created.
/**
* cacheHelper is produced by Spring ApplicationContext getBean, @MockBean has problem,
* so use PowerMock to mock static method SpringContext.getBean to get cacheHelper instance
*/
@PrepareForTest({SpringContext.class})
@PowerMockIgnore({"javax.management.*", "javax.script.*", "org.*", "com.sun.org.apache.xerces.*", "javax.xml.*"})
public class ViewHierarchyTerrServiceTest extends PowerMockTestCase {}
MockMvc
springboot 单元测试 (controller层) 方法 — MockMvc
- mockMvc.perform
执行一个请求
MockMvcRequestBuilders.post(“/appProducer/getAppLatestVersion”)
构造一个请求,get请求就用.get方法
MockHttpServletRequestBuilder.header(“user-agent”, “MicroMessenger”)
请求头设置
MockHttpServletRequestBuilder.param(“appId”, “1001”)
传递参数, get请求也可以通过这种方式传值,也可在地址后加参数方式MockMvcRequestBuilders.get(“/appProducer/getAppLatestVersion?appId=1001”)
contentType(MediaType.APPLICATION_JSON_UTF8)
代表发送端发送的数据格式是application/json;charset=UTF-8
- accept(MediaType.APPLICATION_JSON_UTF8)
代表客户端希望接受的数据类型为application/json;charset=UTF-8
session(session)
注入一个session,这样拦截器才可以通过
ResultActions.andExpect
添加执行完成后的断言
- ResultActions.andExpect(MockMvcResultMatchers.status().isOk())
方法看请求的状态响应码是否为200如果不是则抛异常,测试不通过
- ResultActions.andExpect(jsonPath(“$.status”, not(“E”)))
使用jsonPath解析JSON返回值,判断具体的内容, 此处不希望status返回E,如果返回E测试不通过
- ResultActions.andDo
添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息
@Test
public void create_happyPath() throws Exception {
DeepdrawOrder order = new DeepdrawOrder();
DeepdrawOrderDTO orderDTO = helper.createOrderDTO();
DeepdrawOrderDTO responseOrder = new DeepdrawOrderDTO();
doReturn(order).when(orderController).validateAndConvertToOrder(any(), any());
doReturn(order).when(orderService).create(ArgumentMatchers.isA(DeepdrawOrder.class));
doReturn(responseOrder).when(orderConverter).convertToDTO(ArgumentMatchers.isA(DeepdrawOrder.class));
String RequestBody = JsonUtils.toJson(orderDTO, new TypeReference<DeepdrawOrderDTO>() {});
MvcResult result = mvc.perform(post("/admin/order/create").contentType(APPLICATION_JSON_UTF8).content(RequestBody)).andExpect(status().isOk()).andReturn();
String expected = new ObjectMapper().writeValueAsString(ResponseUtils.success(responseOrder));
assertThat(result.getResponse().getContentAsString(), is(expected));
ArgumentCaptor<DeepdrawOrderDTO> orderDTOCaptor = ArgumentCaptor.forClass(DeepdrawOrderDTO.class);
verify(orderController).validateAndConvertToOrder(any(), orderDTOCaptor.capture());
assertThat(orderDTOCaptor.getValue().getPayType(), is(PayType.ALIPAY));
}
集成测试
新版SpringBoot默认就用一个@SpringBootTest就可以。
test包下application.yml
关于ApplicationContext:
The component classes to use for loading an ApplicationContext. Can also be specified using @ContextConfiguration(classes=…). If no explicit classes are defined the test will look for nested @Configuration classes, before falling back to a @SpringBootConfiguration search. Returns: the component classes used to load the application context
Spring Boot provides a @SpringBootTest annotation, which can be used as an alternative to the standard spring-test @ContextConfiguration annotation when you need Spring Boot features. The annotation works by creating the ApplicationContext used in your tests through SpringApplication. If you are using JUnit 5, there is no need to add the equivalent @ExtendWith(SpringExtension.class) as @SpringBootTest and the other @…Test annotations are already annotated with it.
TestRestTemplate
简易版
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class CustomerDaoTest {
@Autowired
private CustomerDao customerDao;
@Test
public void testFindById() {
Optional<Customer> customer = customerDao.findById(1L);
assertThat(customer.get().getCustId(), Matchers.is(1L));
}
}
AbstractTransactionalJUnit4SpringContextTests
SpringBoot集成测试(参考性不大了)
/**
* 集成测试抽象基类
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {Application.class, RepositoryTestConfig.class})
@Transactional
@Rollback
@Tag("integration")
@TestPropertySource(locations = "classpath:application-test.properties")
@ActiveProfiles({"test", "repo-integration-test"})
public abstract class AbstractRepositoryTest<T> {
@Autowired
protected T repo;
}
jacoco
测试
Entity&Validation
Difference Between @NotNull, @NotEmpty, and @NotBlank Constraints in Bean Validation
Repository
新版不能用@DataJpaTest,直接用@SpringBootTest
Service
```java public class UserServiceImplTest { @InjectMocks private UserServiceImpl userServiceImpl;
@Mock private UserRepository userRepository;
// 新版要使用openMocks @BeforeEach public void init() {
MockitoAnnotations.openMocks(this);
}
// 如果mock了userRepository.findByUsername,但调用的是service.findById,会报空指针 @Test public void findByUsername_shouldReturnMockUser_ifMockReturnAnyUser() {
User foundUser = User.builder().username("Tom2").build();
when(userRepository.findByUsername("Tom")).thenReturn(Optional.of(foundUser));
User resultUser = userServiceImpl.findByUsername("Tom").orElse(null);
Assertions.assertEquals("Tom2", resultUser.getUsername());
} }
<a name="H6mIc"></a>
## controller
[springboot 单元测试 (controller层) 方法 -- MockMvc](https://blog.csdn.net/coolcoffee168/article/details/88638042)<br />[案例:settingControllerTest](http://116.62.122.39/deepdraw/deepdraw/blob/dev/src/test/java/com/soomey/controller/authorized/merchant/setting/SettingControllerTest.java)
<a name="lam0f"></a>
# 案例
不能mock私有函数,只有mock私有函数里的service。 `merchantService.getCurrentMerchant` 。<br />any()参数加具体的类,实际执行时传的是null,则失败,只能any()不加参数。
```java
@ResponseBody
@PostMapping(value = "/productField/batchChangeFieldsStatus")
public String batchChangeFieldsStatus(HttpSession session, @RequestParam("fieldIds") Long[] fieldIds) {
List<ProductField> fields = getFieldsByIds(fieldIds);
Merchant merchant = getCurrentMerchant(session);
doBatchChangeFieldsStatus(fields, merchant);
if (!fields.stream().allMatch(field -> canChangeFieldStatus(field))) {
return ControllerUtils.fail("EMPTY_PRODUCT_FIELD_OPTIONS");
}
return ControllerUtils.success();
}
@Test
public void batchChangeFieldsStatus_shouldBeFail_If_NotAllFieldsCanBeChanged() throws Exception {
Merchant merchant = mock(Merchant.class);
when(merchantService.getCurrentMerchant(any(Session.class))).thenReturn(merchant);
doReturn(false).when(settingContoller).canChangeFieldStatus(any());
doNothing().when(settingContoller).doBatchChangeFieldsStatus(anyList(), any());
MvcResult result = mvc.perform(post("/authorized/merchant/setting/productField/batchChangeFieldsStatus")
.session(new MockHttpSession()).params(composeFieldIdsParams("1"))
).andExpect(status().isOk()).andReturn();
assertEquals("{\"successful\":false,\"errorCode\":\"EMPTY_PRODUCT_FIELD_OPTIONS\"}", result.getResponse().getContentAsString());
}
- SYNNEX GetBestShipMethod
问题
mock final类
待研究@ExtendWith
when-to-use-extendwith-spring-or-mockito@ExtendWith(SpringExtension.class)
@ExtendWith(MockitoExtension.class)
class Test {}
org.hamcrest.Matchers
Matchers.contains(a, b): a、b的顺序也要符合
API
assertThat
// 错误 判断null == null
assertThat(result, isNull());
// 正确
assertNull(result);
抛出错误
关于Java:您如何断言在JUnit 4测试中抛出某个异常?
@Test(expectedExceptions = ArithmeticException.class)
public void givenNumber_whenThrowsException_thenCorrect() {
int i = 1 / 0;
}
publc class Test {
// @Rule
// public ExpectedException expectedException = ExpectedException.none();
@Test
public void getByAccountOrId_shouldBeNull_ifFoundNoneByAccountOrId() {
when(merchantDao.listByAccount(anyString())).thenReturn(Collections.emptyList());
when(entityDao.get(any())).thenThrow(IllegalArgumentException.class);
// 结合mockito使用目前还遇到问题,会报错:Expected test to throw an instance of java.lang.IllegalArgumentException
// expectedException.expect(IllegalArgumentException.class);
Merchant result = service.getByAccountOrId("");
// catch中如果有返回值return null,还是可以断言返回值
assertNull(result);
}
}
@BeforeEach执行时机
@Before
public void setupValidatorInstance() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
坑:idea中如果直接测试某一个方法,不会执行beforeEach,要在类上面点击执行
dataProvider不要用static
用例设计
- 最小用例原则:对于集合的测试,每种条件(不同的项)一个用例 —— 使用singletonList
同一包下
测试代码和源代码要放在同一包下,否则sonar扫描不到。
idea ctrl+shift+T的时候注意路径!Maven/idea执行单元测试
Maven Surefire- This plugin helps in making the execution of unit test cases of a maven project or application and generate the reports of the testing results in the test phase of the build lifecycle.
- There is no need for any extra configurations to specify which provider is used and available for the test cases in the maven project.
- One of the most commonly used and preferred methods of using this plugin is to specify the version of this plugin in the plugins element of your pom.xml file or parent pom.xml file of your maven project.
- An alternative way to use the maven surefire plugin is to call the test phase of the maven builds lifecycle which will invoke this plugin automatically.
- Junit(5.x,3.8 or 4.x version)
- POJO(Plain Old Java Object)
- TestNG