14

Problem

I'm trying to run a simple spring boot test and I'm getting errors that suggest it can't MockMvc at runtime. Documentation suggests I'm using the correct annotations and I created my pom.xml using start.spring.io. Not sure why its having issues.

Error:

 No qualifying bean of type 'org.springframework.test.web.servlet.MockMvc'

TestCode

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyWebApplicationTests {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void Can_Do_Something() throws Exception {
        mockMvc.perform(get("/hello-world")).andDo(print()).andExpect(status().isOk())
                .andExpect(content().string(containsString("Hello World")));
    }

}

Documentation:

I was using this doc as a reference -> https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-with-mock-environment

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>

    <groupId>com.mywebapp</groupId>
    <artifactId>webapp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>my-webapp</name>
    <description>Backend application</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.M1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>10</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.6</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>


</project>
Usman Mutawakil
  • 4,993
  • 9
  • 43
  • 80
  • You're missing the WebMvcTest annotation, specifying which controller you want to test: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests – JB Nizet Aug 13 '18 at 14:11
  • @JBNizet That annotaion seems to collied with AutoConfigureMockMvc when I try adding it. I mean isn't that the point of AutoConfigureMockMvc to inject MockMvC? I assumed this would load the full spring contect and pick the controller by the URL pattern fed to mockMvc. – Usman Mutawakil Aug 13 '18 at 14:16
  • @JBNizet Just updated the actual section of the document I was following -> https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-with-mock-environment – Usman Mutawakil Aug 13 '18 at 14:20
  • Yes, it seems this should indeed do what you describe. I have no idea why it doesn't. – JB Nizet Aug 13 '18 at 14:56
  • @JBNizet At this point I think I'll try opening an issue on Github. – Usman Mutawakil Aug 13 '18 at 19:20
  • 2
    `MockMvc` doesn't apply to a WebFlux application. Use the `WebTestClient` instead. – M. Deinum Aug 13 '18 at 19:45
  • @M.Deinum Thank a lot. Given the overall they did to jump from 4 to 5 reactive I can't blame them for not explicitly stating this in the WebFlux testing examples but damn that would have been appreciated. – Usman Mutawakil Aug 14 '18 at 00:44
  • They didn't jump they added support. You can choose which you want. – M. Deinum Aug 14 '18 at 05:42
  • @M.Deinum Being able to choose doesn't mean it wasn't a big jump from 4 to 5. If anything thats even more work supporting 2 paradigms. – Usman Mutawakil Aug 14 '18 at 11:40
  • 1
    Yes it will. You can even use it to test anything web related. – M. Deinum Jul 19 '19 at 17:43
  • @M.Deinum You are correct. It appears I was missing the reactor-test dependency, but it was not obvious when it failed to work. However, I do remember there being some issue with the testing framework behaving differently if the Routes are used. Perhaps that has to do with the Auto Configuration Annotations instead that will scan for which endpoints to mock automatically for the tests. So it appears `WebFluxTest` is required in that case. – Andrew T Finnell Jul 19 '19 at 18:43

2 Answers2

13

As this Question seems to appear at the top of search lists when people are trying to test their endpoints after they've switched to Spring WebFlux, I'll add what I was able to determine here. (It should be noted that in the past I was having an incredibly hard time getting the WebTestClient to function with RestController annotated endpoints. But this code works. I think I was missing a dependency and it wasn't clear.)

MyService.java

@Service
public class MyService {
     public String doSomething(String input) {
         return input + " implementation";
     }
}

MyController.java

@RestController
@RequestMapping(value = "/api/v1/my")
public class MyController {
    @Autowired
    private MyService myService;

    @RequestMapping(value = "", method = RequestMethod.POST, consumes = {APPLICATION_JSON_VALUE})
    public ResponseEntity<Mono<String>> processPost(@RequestBody String input)
    {
        String response = myService.doSomething(input);
        return ResponseEntity.ok(Mono.just(response));
    }

TestMyController.java

@ExtendWith(SpringExtension.class)
@WebFluxTest(MyController.class)
public class TestMyController {
    @Autowired
    private WebTestClient webTestClient;

    @MockBean
    private MyService myService;

    @Test
    public void testPost() throws Exception {
          // Setup the Mock MyService. Note the 'mocked' vs 'implementation' 
          when(myService.doSomething(anyString())).thenAnswer((Answer<String>) invocation -> {
               String input = invocation.getArgument(0);
               return input + " mocked";
          });

          String response = webTestClient.post()
                .uri("/api/v1/my")
                .body(BodyInserters.fromObject("is"))
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                .expectStatus().isOk()
                .returnResult(String.class)
                .getResponseBody()
                .blockFirst();
          assertThat(response).matches("is mocked");
    }
}

The dependencies that can cause issues that are hard to diagnose appear to be from reactor-test. So if the WebTestClient is not working, make sure that dependency exists.

pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
        <version>2.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.1.5.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-test</artifactId>
        <version>3.2.9.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <artifactId>jackson-module-kotlin</artifactId>
                <groupId>com.fasterxml.jackson.module</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.4.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.google.truth</groupId>
        <artifactId>truth</artifactId>
        <version>0.45</version>
        <scope>test</scope>
    </dependency>
Andrew T Finnell
  • 13,417
  • 3
  • 33
  • 49
  • 2
    Your answer doesn't address MockMvC. The original problem/question was how to use MockMVC with WebFlux, not how to configure a webtest client. – Usman Mutawakil Jul 23 '19 at 21:02
9

As pointed out by M. Deinum MockMvc isn't loaded for the WebFlux configuration in Spring Boot. You need to use WebTestClient instead. So replace AutoConfigureMockMvc with AutoConfigureWebTestClient and utilize the the webTestClient methods in its place.

One thing to note is that this is making actual web calls behind the scenes and will start the server. MockMVC does not start the server. What is the difference between MockMvc and WebTestClient?

Usman Mutawakil
  • 4,993
  • 9
  • 43
  • 80
  • I am not certain what it was that was preventing `WebTestClient` from working before, but it does seem like `WebTestClient` can be utilized with the `WebFlux` `RestController` driven endpoints. – Andrew T Finnell Jul 19 '19 at 18:28
  • @AndrewTFinnell Is the AutoConfigureMockMvc annotation now working with WebFlux? This post was about an inability to use AutoConfigureMockMvc & MockMVC with webflux. – Usman Mutawakil Jul 23 '19 at 20:58
  • 2
    I don't know why this was downvoted, it IS the correct answer, thanks @UsmanMutawakil I couldn't find an explanation anywhere else – sam Apr 04 '20 at 16:24