MockMvc返回了一个带有application/json的HttpMessageNotWritableException。

huangapple go评论88阅读模式
英文:

MockMvc returned HttpMessageNotWritableException with application/json

问题

以下是您提供的代码的翻译部分:

我有一个使用 Spring 2.3.4.RELEASE 的 REST 终端点
当我运行使用 MockMvc 进行控制器测试时我收到以下错误

> w.s.m.s.DefaultHandlerExceptionResolver : 已解析
> [org.springframework.http.converter.HttpMessageNotWritableException:
> 没有适用于 [class com.example.myexample.model.User] 和预设 Content-Type 'application/json' 的转换器]


    @SpringBootTest(classes = UserController.class)
    @ExtendWith(SpringExtension.class)
    @AutoConfigureMockMvc
    public class UserControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @MockBean
        private UserRepository userRepository;
    
        private static final String GET_USERBYID_URL = "/users/{userId}";
        
        @Test
        public void shouldGetUserWhenValid() throws Exception {
            Address address = new Address();
            address.setStreet("1 Abc St.");
            address.setCity("Paris");
    
            User userMock = new User();
            userMock.setFirstname("Lary");
            userMock.setLastname("Pat");
            userMock.setAddress(address);
    
            when(userRepository.findById(1)).thenReturn(Optional.of(userMock));
    
            mockMvc.perform(get(GET_USERBYID_URL, "1").accept(MediaType.APPLICATION_JSON))
                   .andDo(print())
                   .andExpect(status().isOk());
        }
    }

    @RestController
    @RequestMapping(path = "/users")
    @Slf4j
    public class UserController {
    
        @Autowired
        private UserRepository userRepository;
    
        @GetMapping(value = "/{userId}", produces = MediaType.APPLICATION_JSON_VALUE)
        public ResponseEntity<User> getUser(@PathVariable("userId") Integer userId) {
            log.info("获取 id 为{} 的用户", userId);
            final Optional<User> userOptional = userRepository.findById(userId);
    
            return userOptional.map(value -> ResponseEntity.ok()
                                                   .contentType(MediaType.APPLICATION_JSON)
                                                   .body(value))
                       .orElseGet(() -> ResponseEntity.noContent()
                                                      .build()).ok()
                               .body(userOptional.get());
        }
    }
    
    @Data
    public class User {
        private String firstname;
        private String lastname;
        private Address address;
    }
    
    @Data
    public class Address {
        private String street;
        private String city;
    }
    
    @Repository
    public interface UserRepository extends CrudRepository<User, Integer> {
    }

我阅读了其他建议添加 jackson 依赖项的文章但这对我不起作用

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.11.3</version>
</dependency>

这是我的 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 http://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>2.3.4.RELEASE</version>
        <relativePath/> <!-- 从仓库查找父级 -->
    </parent>

    <groupId>org.example</groupId>
    <artifactId>myexample</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <lombok.version>1.18.12</lombok.version>
    </properties>

    <dependencies>
        <!-- 其他依赖项 -->
        <!-- jackson 依赖项 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.11.3</version>
        </dependency>
        <!-- 测试依赖项 -->
        <dependency>
            <!-- 其他依赖项 -->
        </dependency>
    </dependencies>
    <!-- 其他设置 -->
</project>

如果我运行应用并通过 Postman 进行测试端点可以正常工作
有任何想法为什么我在我的 REST 控制器测试中会收到这个错误消息吗

请注意,我只返回了您提供的代码翻译部分,未包含任何额外内容。如果您有更多问题或需要进一步的帮助,请随时提问。

英文:

I have a rest endpoint with spring 2.3.4.RELEASE
When I run test for controller with MockMvc I received

> w.s.m.s.DefaultHandlerExceptionResolver : Resolved
> [org.springframework.http.converter.HttpMessageNotWritableException:
> No converter for [class com.example.myexample.model.User] with preset
> Content-Type 'application/json']

@SpringBootTest(classes = UserController.class)
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserRepository userRepository;
private static final String GET_USERBYID_URL = &quot;/users/{userId}&quot;;
@Test
public void shouldGetUserWhenValid() throws Exception {
Address address = new Address();
address.setStreet(&quot;1 Abc St.&quot;);
address.setCity(&quot;Paris&quot;);
User userMock = new User();
userMock.setFirstname(&quot;Lary&quot;);
userMock.setLastname(&quot;Pat&quot;);
userMock.setAddress(address);
when(userRepository.findById(1)).thenReturn(Optional.of(userMock));
mockMvc.perform(get(GET_USERBYID_URL, &quot;1&quot;).accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk());
}
}
@RestController
@RequestMapping(path = &quot;/users&quot;)
@Slf4j
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping(value = &quot;/{userId}&quot;, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity&lt;User&gt; getUser(@PathVariable(&quot;userId&quot;) Integer userId) {
log.info(&quot;Getting user with id={}&quot;, userId);
final Optional&lt;User&gt; userOptional = userRepository.findById(userId)
return userOptional.map(value -&gt; ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(value))
.orElseGet(() -&gt; ResponseEntity.noContent()
.build()).ok()
.body(userOptional.get());
}
}
@Data
public class User {
private String firstname;
private String lastname;
private Address address;
}
@Data
public class Address {
private String street;
private String city;
}
@Repository
public interface UserRepository extends CrudRepository&lt;User, Integer&gt; {
}

I've read other article that recommended to added jackson dependency, but this doesn't work for me.

&lt;dependency&gt;
&lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
&lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
&lt;version&gt;2.11.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
&lt;artifactId&gt;jackson-core&lt;/artifactId&gt;
&lt;version&gt;2.11.3&lt;/version&gt;
&lt;/dependency&gt;

This is my pom.xml

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot;
xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
&lt;parent&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
&lt;version&gt;2.3.4.RELEASE&lt;/version&gt;
&lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;
&lt;/parent&gt;
&lt;groupId&gt;org.example&lt;/groupId&gt;
&lt;artifactId&gt;myexample&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
&lt;properties&gt;
&lt;java.version&gt;1.8&lt;/java.version&gt;
&lt;spring-cloud.version&gt;Hoxton.SR8&lt;/spring-cloud.version&gt;
&lt;lombok.version&gt;1.18.12&lt;/lombok.version&gt;
&lt;/properties&gt;
&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-validation&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
&lt;version&gt;RELEASE&lt;/version&gt;
&lt;scope&gt;compile&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;com.h2database&lt;/groupId&gt;
&lt;artifactId&gt;h2&lt;/artifactId&gt;
&lt;scope&gt;runtime&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
&lt;artifactId&gt;lombok&lt;/artifactId&gt;
&lt;version&gt;${lombok.version}&lt;/version&gt;
&lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
&lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
&lt;version&gt;2.11.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
&lt;artifactId&gt;jackson-core&lt;/artifactId&gt;
&lt;version&gt;2.11.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- test dependencies --&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
&lt;scope&gt;test&lt;/scope&gt;
&lt;exclusions&gt;
&lt;exclusion&gt;
&lt;groupId&gt;junit&lt;/groupId&gt;
&lt;artifactId&gt;junit&lt;/artifactId&gt;
&lt;/exclusion&gt;
&lt;exclusion&gt;
&lt;groupId&gt;org.junit.vintage&lt;/groupId&gt;
&lt;artifactId&gt;junit-vintage-engine&lt;/artifactId&gt;
&lt;/exclusion&gt;
&lt;/exclusions&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;dependencyManagement&gt;
&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
&lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;
&lt;version&gt;${spring-cloud.version}&lt;/version&gt;
&lt;type&gt;pom&lt;/type&gt;
&lt;scope&gt;import&lt;/scope&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;/dependencyManagement&gt;
&lt;build&gt;
&lt;plugins&gt;
&lt;plugin&gt;
&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
&lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
&lt;version&gt;3.8.1&lt;/version&gt;
&lt;configuration&gt;
&lt;source&gt;${java.version}&lt;/source&gt;
&lt;target&gt;${java.version}&lt;/target&gt;
&lt;/configuration&gt;
&lt;/plugin&gt;
&lt;plugin&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
&lt;/plugin&gt;
&lt;/plugins&gt;
&lt;/build&gt;

</project>

If I run the application and test it through postman, the endpoint works fine.
Any ideas why I get this error message on my rest controller test?

答案1

得分: 7

问题出在你的 @SpringBootTest 上,当你添加了 classes = UserController.class,它告诉 Spring 使用 UserController.class 创建一个上下文。因此,它只会创建包含 UserController 的 Spring 上下文。结果只有你的控制器 bean 被创建了,而其他用于 HTTP 消息转换的 bean 没有被创建。因此,一种解决方案是从 @SpringBootTest 中移除 classes = UserController.class

@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc

现在你应该可以正常运行测试了。

但是这会带来另一个问题,现在它会加载完整的 Spring 上下文,这对于仅进行控制器层测试是不需要的。它还会增加单元测试的时间。

为了解决这个问题,Spring 提供了另一个注解 @WebMvcTest。如果你在测试中使用 @WebMvcTest 注解,Spring Boot 仅会实例化 Web 层,而不是整个上下文。你也可以选择仅实例化一个控制器。例如,

@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

}

这应该可以解决你的问题。

英文:

The problem lies in your @SpringBootTest, when you added classes = UserController.class , it tells spring to create a context with UserController.class. So it create spring context only with UserController. As a result only your controller bean is created and other beans needed for Http Message Converting is not created. So one solution is removing classes = UserController.class from @SpringBootTest.

@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc

Now you should have no problem running the tests.
But this comes with another problem, now this will load the full spring context which is not needed for only Controller Layer Testing. It will also increase the time of your unit tests.
To solve this problem , spring has another annotation @WebMvcTest . If you annotate yous tests with @WebMvcTest, Spring Boot instantiates only the web layer rather than the whole context. You can also choose to instantiate only one controller. For example,

@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
}

This should solve your problem.

答案2

得分: 1

显然我将UserControllerTest放在了错误的包名中。
将UserControllerTest与UserController放在相同的包名下后,它现在可以工作了。

英文:

Apparently I put the UserControllerTest in a wrong package name.
It's working now after I put the UserControllerTest in the same package name as the UserController.

huangapple
  • 本文由 发表于 2020年10月25日 03:39:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/64517342.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定