Skip to content

yukihane/gs-testing-web

 
 

Repository files navigation

This guide walks you through the process of creating a Spring application.

What you’ll build

You’ll build a simple Spring application and test it with JUnit. You probably already know how to write and run unit tests of the individual classes in your application, so for this guide we will concentrate on using Spring Test and Spring Boot features to test the interactions between Spring and your code. You will start with a simple test that the application context loads successfully, and continue on to test just the web layer using Spring’s MockMvc.

Create a simple application

Create a new controller for your Spring application:

src/main/java/hello/HomeController.java

link:complete/src/main/java/hello/HomeController.java[role=include]
Note
The above example does not specify GET vs. PUT, POST, and so forth, because @RequestMapping maps all HTTP operations by default. Use @GetMapping or @RequestMapping(method=GET) to narrow this mapping.

Make the application executable

Although it is possible to package this service as a traditional WAR file for deployment to an external application server, the simpler approach demonstrated below creates a standalone application. You package everything in a single, executable JAR file, driven by a good old Java main() method. Along the way, you use Spring’s support for embedding the Tomcat servlet container as the HTTP runtime, instead of deploying to an external instance.

src/main/java/hello/Application.java

link:complete/src/main/java/hello/Application.java[role=include]

@SpringBootApplication is a convenience annotation that adds all of the following:

  • @Configuration tags the class as a source of bean definitions for the application context.

  • @EnableAutoConfiguration tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.

  • Normally you would add @EnableWebMvc for a Spring MVC app, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath. This flags the application as a web application and activates key behaviors such as setting up a DispatcherServlet.

  • @ComponentScan tells Spring to look for other components, configurations, and services in the the hello package, allowing it to find the HelloController.

The main() method uses Spring Boot’s SpringApplication.run() method to launch an application. Did you notice that there wasn’t a single line of XML? No web.xml file either. This web application is 100% pure Java and you didn’t have to deal with configuring any plumbing or infrastructure.

Logging output is displayed. The service should be up and running within a few seconds.

Test the application

Now that the application is running, you can test it. If it is running you can load the home page on http://localhost:8080. But to give yourself more confidence that the application is working when you make changes, you want to automate the testing.

The first thing you can do is write a simple sanity check test that will fail if the application context cannot start. First add Spring Test as a dependency to your pom.xml, in the test scope. If you are using Maven:

pom.xml

link:complete/pom.xml[role=include]

or if you are using Gradle:

build.gradle

link:complete/build.gradle[role=include]

Then create a test case with the @RunWith and @SpringBootTest annotations and an empty test method:

src/test/java/hello/ApplicationTest.java

package hello;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {

    @Test
    public void contextLoads() throws Exception {
    }

}

The @SpringBootTest annotation tells Spring Boot to go and look for a main configuration class (one with @SpringBootApplication for instance), and use that to start a Spring application context. You can run this test in your IDE or on the command line (mvn test or gradle test) and it should pass. To convince yourself that the context is creating your controller you could add an assertion:

src/test/java/hello/SmokeTest.java

link:complete/src/test/java/hello/SmokeTest.java[role=include]

The @Autowired annotation is interpreted by the Spring and the controller is injected before the test methods are run. We are using AssertJ (assertThat() etc.) to express the test assertions.

Note
A nice feature of the Spring Test support is that the application context is cached in between tests, so if you have multiple methods in a test case, or multiple test cases with the same configuration, they only incur the cost of starting the application once. You can control the cache using the @DirtiesContext annotation.

It’s nice to have a sanity check like that, but we should also write some tests that assert the behaviour of our application. To do that we could start the application up and listen for a connection like it would do in production, and then send an HTTP request and assert the response.

src/test/java/hello/HttpRequestTest.java

link:complete/src/test/java/hello/HttpRequestTest.java[role=include]

Note the use of webEnvironment=RANDOM_PORT to start the server with a random port (useful to avoid conflicts in test environments), and the injection of the port with @LocalServerPort. Also note that Spring Boot has provided a TestRestTemplate for you automatically, and all you have to do is @Autowired it.

Another useful approach is to not start the server at all, but test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost the full stack is used, and your code will be called exactly the same way as if it was processing a real HTTP request, but without the cost of starting the server. To do that we will use Spring’s MockMvc, and we can ask for that to be injected for us by using the @AutoConfigureMockMvc annotation on the test case:

src/test/java/hello/ApplicationTest.java

link:complete/src/test/java/hello/ApplicationTest.java[role=include]

In this test, the full Spring application context is started, but without the server. We can narrow down the tests to just the web layer by using @WebMvcTest:

src/test/java/hello/WebLayerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest
link:complete/src/test/java/hello/WebLayerTest.java[role=include]

The test assertion is the same as in the previous case, but here Spring Boot is only instantiating the web layer, not the whole context. In an application with multiple controllers you can even ask for just one to be instantiated, using, for example @WebMvcTest(HomeController.class)

So far, our HomeController is very simple and has no dependencies. We could make it more realistic by introducing an extra component to store the greeting. E.g. in a new controller:

src/main/java/hello/GreetingController.java

link:complete/src/main/java/hello/GreetingController.java[role=include]

and then

src/main/java/hello/GreetingService.java

link:complete/src/main/java/hello/GreetingService.java[role=include]

The service dependency will be automatically injected by Spring into the controller (because of the constructor signature). To test this controller with @WebMvcTest you can do this

src/test/java/hello/WebMockTest.java

link:complete/src/test/java/hello/WebMockTest.java[role=include]

We use @MockBean to create and inject a mock for the GreetingService (if you don’t do this the application context cannot start), and we set its expectations using Mockito.

Summary

Congratulations! You’ve just developed a Spring application and tested it with JUnit and Spring MockMvc using Spring Boot to isolate the web layer and load a special application context.

About

JUnit Jupiter マイグレーション

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Java 94.0%
  • Shell 6.0%