user293655
user293655

Reputation: 558

MockMvc returned HttpMessageNotWritableException with application/json

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 = "/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("Getting user with 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> {
}

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

<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>

This is my 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/> <!-- lookup parent from repository -->
    </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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <scope>provided</scope>
    </dependency>

    <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>



    <!-- test dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

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?

Upvotes: 0

Views: 2809

Answers (2)

user293655
user293655

Reputation: 558

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.

Upvotes: 1

Shawrup
Shawrup

Reputation: 2744

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.

Upvotes: 7

Related Questions