This guide walks you through the process of creating a Spring application.
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 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.
|
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 aDispatcherServlet
. -
@ComponentScan
tells Spring to look for other components, configurations, and services in the thehello
package, allowing it to find theHelloController
.
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.
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
.
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.
The following guides may also be helpful: