Spring Boot Mockito (一)
这篇文章主要是讲解Spring boot 与 Mockito 集成API接口层单元测试。
1. 引入相关依赖包
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>
2. 新建订单类
java
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Setter
@Getter
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Double price;
@Column(name = "create_time")
// @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@Column(name = "update_time")
// @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}
3. 新建订单DAO
继承 JpaRepository
, 包含基本的CRUD
java
public interface OrderRepository extends JpaRepository<Order, Long> {
}
4. 新建订单Service
基本的CRUD
java
public interface OrderService {
Optional<Order> update(Long id, Order order);
Order save(Order order);
List<Order> getOrders();
Optional<Order> getOrderById(Long id);
Boolean delete(Long id);
}
6. 新建订单Service 实现
java
@Service
public class OrderServiceImpl implements OrderService {
private OrderRepository orderRepository;
public OrderServiceImpl(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Override
public Optional<Order> update(Long id, Order order) {
AtomicReference<Optional<Order>> atomicReference = new AtomicReference<>();
orderRepository.findById(id).ifPresentOrElse(foundVal -> {
updateOrder(order, foundVal);
atomicReference.set(Optional.of(orderRepository.save(foundVal)));
}, () -> atomicReference.set(Optional.empty()));
return atomicReference.get();
}
public Order save(Order order) {
if (null == order.getId()) {
order.setCreateTime(LocalDateTime.now());
return orderRepository.save(order);
}
return null;
}
@Override
public List<Order> getOrders() {
return orderRepository.findAll();
}
@Override
public Optional<Order> getOrderById(Long id) {
return orderRepository.findById(id);
}
@Override
public Boolean delete(Long id) {
if (getOrderById(id).isEmpty()) {
return false;
}
orderRepository.deleteById(id);
return true;
}
private void updateOrder(Order current, Order newOrder) {
if (null != current.getPrice()) {
newOrder.setPrice(current.getPrice());
newOrder.setName(current.getName());
newOrder.setUpdateTime(LocalDateTime.now());
}
}
}
7. 新建订单Controller
包含基本的CRUD接口
java
@RestController
@RequestMapping(ORDER_PATH)
public class OrderController {
public static final String ORDER_PATH = "/api/v1/orders";
private OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
Order savedOrder = orderService.save(order);
return new ResponseEntity<>(savedOrder, HttpStatus.CREATED);
}
@GetMapping
public ResponseEntity<List<Order>> getAllOrders() {
return ResponseEntity.ok(orderService.getOrders());
}
@GetMapping("{id}")
public ResponseEntity<Order> getOrderById(@PathVariable("id") long orderId) {
return orderService.getOrderById(orderId)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PutMapping("{id}")
public ResponseEntity<Void> updateOrder(@PathVariable("id") long orderId,
@RequestBody Order order) {
if (orderService.update(orderId, order).isEmpty()) {
throw new NotFoundException();
}
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@DeleteMapping("{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable("id") long orderId) {
orderService.delete(orderId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
8. 新建异常类
java
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Value Not Found")
public class NotFoundException extends RuntimeException {
public NotFoundException() {
super();
}
public NotFoundException(String message) {
super(message);
}
public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
public NotFoundException(Throwable cause) {
super(cause);
}
protected NotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
9. 新建测试类
java
@WebMvcTest(BeerController.class)
class BeerControllerTest {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@MockBean
BeerService beerService;
BeerServiceImpl beerServiceImpl;
@Captor
ArgumentCaptor<UUID> uuidArgumentCaptor;
@Captor
ArgumentCaptor<BeerDTO> beerArgumentCaptor;
@BeforeEach
void setUp() {
beerServiceImpl = new BeerServiceImpl();
}
@Test
void testPatchBeer() throws Exception {
BeerDTO beer = beerServiceImpl.listBeers(null, null, false, 1, 25).get(0);
Map<String, Object> beerMap = new HashMap<>();
beerMap.put("beerName", "New Name");
mockMvc.perform(patch(BeerController.BEER_PATH_ID, beer.getId())
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(beerMap)))
.andExpect(status().isNoContent());
verify(beerService).patchBeerById(uuidArgumentCaptor.capture(), beerArgumentCaptor.capture());
assertThat(beer.getId()).isEqualTo(uuidArgumentCaptor.getValue());
assertThat(beerMap.get("beerName")).isEqualTo(beerArgumentCaptor.getValue().getBeerName());
}
@Test
void testDeleteBeer() throws Exception {
BeerDTO beer = beerServiceImpl.listBeers(null, null, false, 1, 25).get(0);
given(beerService.deleteBeerById(any())).willReturn(true);
mockMvc.perform(delete(BeerController.BEER_PATH_ID, beer.getId())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
//ArgumentCaptor<UUID> uuidArgumentCaptor = ArgumentCaptor.forClass(UUID.class);
verify(beerService).deleteBeerById(uuidArgumentCaptor.capture());
assertThat(beer.getId()).isEqualTo(uuidArgumentCaptor.getValue());
}
@Test
void testUpdateBeer() throws Exception {
BeerDTO beer = beerServiceImpl.listBeers(null, null, false, 1, 25).get(0);
given(beerService.updateBeerById(any(), any())).willReturn(Optional.of(beer));
mockMvc.perform(put(BeerController.BEER_PATH_ID, beer.getId())
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(beer)))
.andExpect(status().isNoContent());
verify(beerService).updateBeerById(any(UUID.class), any(BeerDTO.class));
//verify(beerService).updateBeerById(beer.getId(), any(Beer.class));
}
@Test
void testCreateNewBeer() throws Exception {
BeerDTO beer = beerServiceImpl.listBeers(null, null, false, 1, 25).get(0);
//System.out.println(objectMapper.writeValueAsString(beer));
beer.setVersion(null);
beer.setId(null);
given(beerService.saveNewBeer(any(BeerDTO.class)))
.willReturn(beerServiceImpl.listBeers(null, null, false, 1, 25).get(1));
mockMvc.perform(post(BeerController.BEER_PATH)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(beer))
)
.andExpect(status().isCreated())
.andExpect(header().exists("Location"));
}
@Test
void testCreateNullBearName() throws Exception {
BeerDTO beerDTO = BeerDTO.builder().build();
given(beerService.saveNewBeer(any(BeerDTO.class)))
.willReturn(beerServiceImpl.listBeers(null, null, false, 1, 25).get(1));
MvcResult mvcResult = mockMvc.perform(post(BeerController.BEER_PATH)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(beerDTO))
)
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.length()", is(6)))
.andReturn();
System.out.println(mvcResult.getResponse().getContentAsString());
}
@Test
void testListBeers() throws Exception {
given(beerService.listBeers(any(), any(), any(), any(), any()))
.willReturn(beerServiceImpl.listBeers(null, null, false, null, null));
mockMvc.perform(get(BeerController.BEER_PATH)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.length()", is(3)));
}
@Test
void getBeerByIdNotFound() throws Exception {
given(beerService.getBeerById(any(UUID.class)))
.willThrow(NotFoundException.class);
mockMvc.perform(get(BeerController.BEER_PATH_ID, UUID.randomUUID()))
.andExpect(status().isNotFound());
}
@Test
void getBeerById() throws Exception {
BeerDTO testBeer = beerServiceImpl.listBeers(null, null, false, 1, 25).get(0);
given(beerService.getBeerById(testBeer.getId())).willReturn(Optional.of(testBeer));
mockMvc.perform(get(BeerController.BEER_PATH_ID, testBeer.getId())
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.id", is(testBeer.getId().toString())))
.andExpect(jsonPath("$.beerName", is(testBeer.getBeerName())))
;
}
}
9. 执行单元测试
@WebMvcTest
Spring Boot的MVC Web Controller
@DataJpaTest
Spring Boot的JPA Queries
@SpringBootTest
集成测试