This guide walks you through the process of creating a Spring application and then testing it with JUnit.
You will 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 only the web layer by using
Spring’s MockMvc
.
For all Spring applications, you should start with the Spring Initializr. The Initializr offers a fast way to pull in all the dependencies you need for an application and does a lot of the setup for you. This example needs only the Spring Web dependency.
The following listing shows the pom.xml
file that is created when you choose Maven:
link:initial/pom.xml[role=include]
The following listing shows the build.gradle
file that is created when you choose Gradle:
link:initial/build.gradle[role=include]
Create a new controller for your Spring application. The following listing (from
src/main/java/com/example/testingweb/HomeController.java
) shows how to do so:
link:complete/src/main/java/com/example/testingweb/HomeController.java[role=include]
Note
|
The preceding example does not specify GET versus PUT , POST , and so forth.
By default @RequestMapping maps all HTTP operations. You can use @GetMapping or
@RequestMapping(method=GET) to narrow this mapping.
|
The Spring Initializr creates an application class (a class with a main()
method) for
you. For this guide, you need not modify this class. The following listing (from
src/main/java/com/example/testingweb/TestingWebApplication.java
) shows the application class that
the Spring Initializr created:
link:complete/src/main/java/com/example/testingweb/TestingWebApplication.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. -
@EnableWebMvc
: Flags the application as a web application and activates key behaviors, such as setting up aDispatcherServlet
. Spring Boot adds it automatically when it seesspring-webmvc
on the classpath. -
@ComponentScan
: Tells Spring to look for other components, configurations, and services in the thecom.example.testingweb
package, letting it find theHelloController
class.
The main()
method uses Spring Boot’s SpringApplication.run()
method to launch an
application. Did you notice that there is not a single line of XML? There is no web.xml
file, either. This web application is 100% pure Java and you did not have to deal with
configuring any plumbing or infrastructure. Spring Boot handles all of that for you.
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. You can load the home page at
http://localhost:8080
. However, to give yourself more confidence that the application
works when you make changes, you want to automate the testing.
Note
|
Spring Boot assumes you plan to test your application, so it adds the necessary
dependencies to your build file (build.gradle or pom.xml ).
|
The first thing you can do is write a simple sanity check test that will fail if the
application context cannot start. The following listing (from
src/test/java/com/example/testingweb/TestingWebApplicationTest.java
) shows how to do so:
link:initial/src/test/java/com/example/testingweb/TestingWebApplicationTests.java[role=include]
The @SpringBootTest
annotation tells Spring Boot to 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 (by running
./mvnw test
or ./gradlew test
), and it should pass. To convince yourself that the
context is creating your controller, you could add an assertion, as the following example
(from src/test/java/com/example/testingweb/SmokeTest.java
) shows:
link:complete/src/test/java/com/example/testingweb/SmokeTest.java[role=include]
Spring interprets the @Autowired
annotation, and the controller is injected before the
test methods are run. We use AssertJ
(which provides assertThat()
and other methods) to express the test assertions.
Note
|
A nice feature of the Spring Test support is that the application context is cached
between tests. That way, if you have multiple methods in a test case or multiple test
cases with the same configuration, they incur the cost of starting the application only
once. You can control the cache by using the @DirtiesContext
annotation.
|
It is nice to have a sanity check, but you should also write some tests that assert the
behavior of your application. To do that, you could start the application and listen for a
connection (as it would do in production) and then send an HTTP request and assert the
response. The following listing (from
src/test/java/com/example/testingweb/HttpRequestTest.java
) shows how to do so:
link:complete/src/test/java/com/example/testingweb/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 automatically provided a
TestRestTemplate
for you. All you have to do is add @Autowired
to it.
Another useful approach is to not start the server at all but to test only the layer below
that, where Spring handles the incoming HTTP request and hands it off to your controller.
That way, almost of the full stack is used, and your code will be called in exactly the
same way as if it were processing a real HTTP request but without the cost of starting the
server. To do that, use Spring’s MockMvc
and ask for that to be injected for you by
using the @AutoConfigureMockMvc
annotation on the test case. The following listing (from
src/test/java/com/example/testingweb/TestingWebApplicationTest.java
) shows how to do so:
link:complete/src/test/java/com/example/testingweb/TestingWebApplicationTest.java[role=include]
In this test, the full Spring application context is started but without the server. We
can narrow the tests to only the web layer by using @WebMvcTest
, as the following
listing (from src/test/java/com/example/testingweb/WebLayerTest.java
) shows:
@WebMvcTest
link:complete/src/test/java/com/example/testingweb/WebLayerTest.java[role=include]
The test assertion is the same as in the previous case. However, in this test, Spring Boot
instantiates only the web layer rather than the whole context. In an application with
multiple controllers, you can even ask for only one to be instantiated by using, for
example, @WebMvcTest(HomeController.class)
.
So far, our HomeController
is simple and has no dependencies. We could make it more
realistic by introducing an extra component to store the greeting (perhaps in a new
controller). The following example (from
src/main/java/com/example/testingweb/GreetingController.java
) shows how to do so:
link:complete/src/main/java/com/example/testingweb/GreetingController.java[role=include]
Then create a greeting service, as the following listing (from
src/main/java/com/example/testingweb/GreetingService.java
) shows:
link:complete/src/main/java/com/example/testingweb/GreetingService.java[role=include]
Spring automatically injects the service dependency into the controller (because of the
constructor signature). The following listing (from
src/test/java/com/example/testingweb/WebMockTest.java
) shows how to test this controller
with @WebMvcTest
:
link:complete/src/test/java/com/example/testingweb/WebMockTest.java[role=include]
We use @MockBean
to create and inject a mock for the GreetingService
(if you do not do
so, the application context cannot start), and we set its expectations using Mockito
.
Congratulations! You have developed a Spring application and tested it with JUnit and
Spring MockMvc
and have used Spring Boot to isolate the web layer and load a special
application context.
The following guides may also be helpful: