Spring Boot的 Mockito测试中的未满足的依赖异常
我在学习Mockito进行单元测试时,遇到了一个错误
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.codefinity.com.codefinity.firstrestapibooks.controller.BookControllerTest': Unsatisfied dependency expressed through field 'objectMapper': No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:767)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:748)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:493)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1446)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:402)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:157)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:111)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260)
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:242)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:186)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1716)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:153)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:176)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:632)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:2297)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1725)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:764)
... 23 more
我在测试一个控制器,代码在这里
package org.codefinity.com.codefinity.firstrestapibooks.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.codefinity.com.codefinity.firstrestapibooks.DTO.BookRequestDTO;
import org.codefinity.com.codefinity.firstrestapibooks.DTO.BookResponseDTO;
import org.codefinity.com.codefinity.firstrestapibooks.model.Book;
import org.codefinity.com.codefinity.firstrestapibooks.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
@Tag(name="Book",description = "API for managing books")
public class BookController {
private final BookService bookService;
public BookController(BookService bookService){
this.bookService = bookService;
}
@GetMapping
@Operation(summary="Get the list of all books")
@ApiResponse(responseCode = "200", description = "books successfully retrieved")
public List<BookResponseDTO> findAllBooks(){
return bookService.findAllBooks();
}
@PostMapping
@Operation(summary = "Create a new book")
@ApiResponse(responseCode = "200", description = "Book Sucessfully created")
public BookResponseDTO createBook(@RequestBody BookRequestDTO book){
return bookService.createBook(book);
}
@PutMapping("/{id}")
@Operation(summary = "Update and existing book by ID")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Book succesfully updated"),
@ApiResponse(responseCode = "404", description = "Id not found")} )
public BookResponseDTO updateBook(@Parameter(description = "Id of book to be updated")@PathVariable String id, @RequestBody BookRequestDTO book){
return bookService.updateBook(id, book);
}
@DeleteMapping("/{id}")
@Operation(summary = "Delets a book by its ID")
@ApiResponse(responseCode = "200", description = "id of book to be deleted")
public void deleteBook(@Parameter(description = "id of the book to be deleted",required = true) @PathVariable String id){
bookService.deleteBook(id);
}
@GetMapping("/author/{author}")
public List<BookResponseDTO>findByAuthor(@PathVariable String author){
return bookService.findBookByAuthor(author);
}
}
我在测试update updateBook方法,测试代码在这里
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest
public class BookControllerTest {
@MockitoBean
private BookServiceImpl bookService;
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
private BookRequestDTO bookRequestDTO;
private BookResponseDTO bookResponseDTO;
@BeforeEach
void setUp() {
bookRequestDTO = new BookRequestDTO();
bookRequestDTO.setAuthor("authorTest");
bookRequestDTO.setName("nameTest");
bookRequestDTO.setPrice("priceTest");
bookResponseDTO = new BookResponseDTO();
bookResponseDTO.setId("1");
bookResponseDTO.setAuthor("authorTest");
bookResponseDTO.setName("nameTest");
bookResponseDTO.setPrice("priceTest");
}
@Test
void testUpdateBook_whenBookExists_shouldReturnUpdateBook() throws Exception{
String bookId="1";
when(bookService.updateBook(bookId, bookRequestDTO)).thenReturn(bookResponseDTO);
mockMvc.perform(put("/books/{id}",bookId)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(bookRequestDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(bookResponseDTO.getId()))
.andExpect(jsonPath("$.name").value(bookResponseDTO.getName()))
.andExpect(jsonPath("$.author").value(bookResponseDTO.getAuthor()))
.andExpect(jsonPath("$.price").value(bookResponseDTO.getPrice()));
verify(bookService).updateBook(bookId, bookRequestDTO);
}
}
我遇到的主要问题是这行.content(objectMapper.writeValueAsString(bookRequestDTO))
应用程序在使用被 @Autowired注解的ObjectMapper时出现问题。我尝试将其切换为 @MockitoBean,但仍然出现如下错误
java.lang.NullPointerException: Cannot invoke "String.getBytes(java.nio.charset.Charset)" because "content" is null
at org.springframework.test.web.servlet.request.AbstractMockHttpServletRequestBuilder.content(AbstractMockHttpServletRequestBuilder.java:301)
at org.codefinity.com.codefinity.firstrestapibooks.controller.BookControllerTest.testUpdateBook_whenBookExists_shouldReturnUpdateBook(BookControllerTest.java:64)
我不确定问题出在哪里。下面是我的pom.xml,我在想这可能是依赖问题
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.codefinity</groupId>
<artifactId>first-rest-api-books</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>com.codefinity.firstrestapiooks</name>
<description>com.codefinity.firstrestapiooks</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>25</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
<version>4.1.0-RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>3.0.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>4.1.0-RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<version>4.1.0-RC1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.7.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
<version>12.4.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.43.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>6.1.0-RC1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
<version>4.1.0-RC1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>7.4.0.Final</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.46</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
我会在这里再补充BookRequestDTO和 BookResponseDTO,以防万一
package org.codefinity.com.codefinity.firstrestapibooks.DTO;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
public class BookRequestDTO {
private String name;
private String author;
private String price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
package org.codefinity.com.codefinity.firstrestapibooks.DTO;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
public class BookResponseDTO {
private String id;
private String name;
private String author;
private String price;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
请告诉我有哪些解答,谢谢。
解决方案
你现在使用的是Spring Boot 4,它将Jackson 3作为默认的JSON库,包名从 com.fasterxml.jackson 改为 tools.jackson。
你的测试自动装配了 ObjectMapper(Jackson 2),但上下文中自动配置的bean现在是 JsonMapper(Jackson 3)。该类型在bean中不存在,因此会报错。
修复自动装配字段:
java
@Autowired
private JsonMapper objectMapper;
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。