When individual program units are combined and tested as a group, then it is known as integration testing. The Spring Test context framework provides first-class support for an integration test of a Spring-based application. We have defined lots of Spring managed beans in our web application context, such as services, repositories, view resolvers, and more, to run our application.
These managed beans are instantiated during the startup of an application by the Spring framework. While doing the integration testing, our test environment must also have those beans to test our application successfully. The Spring Test context framework gives us the ability to define a test context that is similar to the web application context. Let's see how to incorporate the Spring Test context to test our ProductValidator
class.
Let's see how we can boot up our test context using the Spring Test context framework to test our ProductValidator
class:
pom.xml
, which you can find under the root directory of the project itself.pom.xml
file; select the Dependencies tab and click on the Add button of the Dependencies section.org.springframework
as Group Id, spring-test
as Artifact Id, 4.3.0.RELEASE
as Version, and select test as Scope, then click on the OK button and save pom.xml
.javax.servlet
, Artifact Id as jsp-api
, and Version as 2.0
, but this time, select Scope as test and then click on the OK button and save pom.xml
.ProductValidatorTest
under the com.packt.webstore.validator
package in the src/test/java
source folder, and add the following code to it:package com.packt.webstore.validator; import java.math.BigDecimal; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory .annotation.Autowired; import org.springframework.test .context.ContextConfiguration; import org.springframework.test.context .junit4.SpringJUnit4ClassRunner; import org.springframework.test .context.web.WebAppConfiguration; import org.springframework.validation.BindException; import org.springframework .validation.ValidationUtils; import com.packt.webstore.config .WebApplicationContextConfig; import com.packt.webstore.domain.Product; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = WebApplicationContextConfig.class) @WebAppConfiguration public class ProductValidatorTest { @Autowired private ProductValidator productValidator; @Test public void product_without_UnitPrice_should_be_invalid() { //Arrange Product product = new Product(); BindException bindException = new BindException(product, " product"); //Act ValidationUtils.invokeValidator (productValidator, product, bindException); //Assert Assert.assertEquals(1, bindException.getErrorCount()); Assert.assertTrue(bindException .getLocalizedMessage().contains("Unit price is Invalid. It cannot be empty.")); } @Test public void product_with_existing_productId_invalid() { //Arrange Product product = new Product("P1234","iPhone 5s", new BigDecimal(500)); product.setCategory("Tablet"); BindException bindException = new BindException(product, " product"); //Act ValidationUtils.invokeValidator (productValidator, product, bindException); //Assert Assert.assertEquals(1, bindException.getErrorCount()); Assert.assertTrue(bindException. getLocalizedMessage().contains("A product already exists with this product id.")); } @Test public void a_valid_product_should_not_get _any_error_during_validation() { //Arrange Product product = new Product("P9876","iPhone 5s", new BigDecimal(500)); product.setCategory("Tablet"); BindException bindException = new BindException(product, " product"); //Act ValidationUtils.invokeValidator (productValidator, product, bindException); //Assert Assert.assertEquals(0, bindException.getErrorCount()); } }
ProductValidatorTest
and choose Run As | JUnit Test. You will be able to see passing test cases, as shown in the following screenshot:As I already mentioned, Spring provides extensive support for integration testing. In order to develop a test case using the Spring Test context framework, we need the required spring-test
JAR. In step 3, we just added a dependency to the spring-test
JAR. The Spring Test context framework cannot run without the support of the JUnit
JAR.
Step 5 is very important because it represents the actual test class (ProductValidatorTest
) to test the validity of our Product
domain object. The goal of the test class is to check whether all the validations (including bean validation and Spring validation) that were specified in the Product
domain class are working. I hope you remember that we specified some of the bean validation annotations such as @NotNull
, @Pattern
, and more in the Product
domain class.
One way to test whether those validations are taking place is by manually running our application and trying to enter invalid values. This approach is called manual testing. This is a very difficult job, whereas in automated testing we can write some test classes to run test cases in repeated fashion to test our functionality. Using JUnit, we can write this kind of test class.
The ProductValidatorTest
class contains three test methods in total; we can identify a test method using the @Test
(org.junit.Test
) annotation of JUnit. Every test method can be logically separated into three parts, that is, Arrange
, Act
, and Assert
. In the Arrange
part, we instantiated and instrumented the required objects for testing; in the Act
part, we invoked the actual functionality that needs to be tested; and finally in the Assert
part, we compared the expected result and the actual result that is an output of the invoked functionality:
@Test public void product_without_UnitPrice_should_be_invalid() { //Arrange Product product = new Product(); BindException bindException = new BindException(product, " product"); //Act ValidationUtils.invokeValidator(productValidator, product, bindException); //Assert Assert.assertEquals(1, bindException.getErrorCount()); Assert.assertTrue(bindException.getLocalizedMessage().contains("Unit price is Invalid. It cannot be empty.")); }
In the Arrange
part of this test method, we just instantiated a bare minimum Product
domain object. We have not set any values for the productId
, unitPrice
, and category
fields. We purposely set up such a bare minimum domain object in the Arrange
part to check whether our ProductValidator
class is working properly in the Act
part.
According to the ProductValidator
class logic, the present state of the product
domain object is invalid. And in the Act
part, we invoked the productValidator
method using the ValidationUtils
class to check whether the validation works or not. During validation, productValidator
will store the errors in a BindException
object. In the Arrange
part, we simply check whether the bindException
object contains one error using the JUnit Assert
APIs, and check that the error message was as expected.
Another important thing you need to understand in our ProductValidatorTest
class is that we used a Spring standard @Autowired
annotation to get the instance of ProductValidator
. The question here is who instantiated the productValidator
object? The answer is in the @ContextConfiguration
annotation. Yes, if you looked at the classes
attribute specified in the @ContextConfiguration
annotation, it has the name of our test context file (WebApplicationContextConfig.class
).
If you remember correctly, you learned in the past that during the booting up of our application, Spring MVC creates a web application context (Spring container) with the necessary beans, as defined in the web application context configuration file. We need a similar kind of context even before running our test classes, so that we can use those defined beans (objects) in our test class to test them properly. The Spring Test framework makes this possible via the @ContextConfiguration
annotation.
Likewise, we need a similar running application environment with all the resource files, and to achieve this we used the @WebAppConfiguration
annotation from the Spring Test framework. The @WebAppConfiguration
annotation instructs the Spring Test framework to load the application context as WebApplicationContext
.
Now you have seen almost all the important things related to executing a Spring integration test, but there is one final configuration you need to understand, how to integrate JUnit and the Spring Test context framework into our test class. The @RunWith(SpringJUnit4ClassRunner.class)
annotation just does this job.
So finally, when we run our test cases, we are able to see a green bar in the JUnit window indicating that the tests were successful.
Now let's look at how to test our Controllers:
ProductControllerTest
under the com.packt.webstore.controller
package in the src/test/java
source folder and add the following code to it:package com.packt.webstore.controller; import static org.springframework.test.web.servlet .request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet .result.MockMvcResultMatchers.model; import java.math.BigDecimal; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory .annotation.Autowired; import org.springframework.test.context .ContextConfiguration; import org.springframework.test.context .junit4.SpringJUnit4ClassRunner; import org.springframework.test.context .web.WebAppConfiguration; import org.springframework.test.web .servlet.MockMvc; import org.springframework.test.web .servlet.setup.MockMvcBuilders; import org.springframework.web .context.WebApplicationContext; import com.packt.webstore.config .WebApplicationContextConfig; import com.packt.webstore.domain.Product; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = WebApplicationContextConfig.class) @WebAppConfiguration public class ProductControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test public void testGetProducts() throws Exception { this.mockMvc.perform(get("/market/products")) .andExpect(model(). attributeExists("products")); } @Test public void testGetProductById() throws Exception { //Arrange Product product = new Product("P1234","iPhone 5s", new BigDecimal(500)); //Act & Assert this.mockMvc.perform(get("/market/product") .param("id", "P1234")) .andExpect(model().attributeExists("product")) .andExpect(model().attribute("product", product)); } }
ProductControllerTest
class and choose Run As | JUnit Test. You will be able to see that the test cases are being executed, and you will be able to see the test results in the JUnit window.Similar to the ProductValidatorTest
class, we need to boot up the test context and want to run our ProductControllerTest
class as a Spring integration test. So we used similar annotations on top of ProductControllerTest
as follows:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = WebApplicationContextConfig.class) @WebAppConfiguration public class ProductControllerTest {
As well as the two test methods that are available under ProductControllerTest
, a single setup method is available as follows:
@Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); }
The @Before
annotation that is present on top the previous method indicates that this method should be executed before every test method. And within that method, we simply build our mockMvc
object in order to use it in the following test methods. The MockMvc
class is a special class provided by the Spring Test context framework to simulate browser actions within a test case, such as firing HTTP requests:
@Test public void testGetProducts() throws Exception { this.mockMvc.perform(get("/market/products")) .andExpect(model().attributeExists("products")); }
This test method simply fires a GET HTTP request to our application using the mockMvc
object, and as a result, we ensure the returned model contains an attribute named products
. Remember that our list
method from the ProductController
class is the thing handling the previous web request, so it will fill the model with the available products under the attribute name products
.
After running our test case, you are able to see the green bar in the JUnit window, which indicates that the tests passed.
Similarly, we can test the REST-based Controllers as well—just follow these steps:
pom.xml
, which you can find pom.xml
under the root directory of the project itself.pom.xml
file; select the Dependencies tab and click on the Add button of the Dependencies section.com.jayway.jsonpath
, for Artifact Id enter json-path-assert
, for Version enter 2.2.0
, and for Scope select test, then click on the OK button and save pom.xml
.CartRestControllerTest
under the com.packt.webstore.controller
package in the src/test/java
source folder, and add the following code to it:package com.packt.webstore.controller; import static org.springframework.test.web .servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web .servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web .servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web .servlet.result.MockMvcResultMatchers.status; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory .annotation.Autowired; import org.springframework.mock .web.MockHttpSession; import org.springframework.test.context .ContextConfiguration; import org.springframework.test .context.junit4.SpringJUnit4ClassRunner; import org.springframework.test .context.web.WebAppConfiguration; import org.springframework .test.web.servlet.MockMvc; import org.springframework.test.web .servlet.setup.MockMvcBuilders; import org.springframework.web.context .WebApplicationContext; import com.packt.webstore.config .WebApplicationContextConfig; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = WebApplicationContextConfig.class) @WebAppConfiguration public class CartRestControllerTest { @Autowired private WebApplicationContext wac; @Autowired MockHttpSession session; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test public void read_method_should_return_correct_cart_Json_object () throws Exception { //Arrange this.mockMvc.perform(put("/rest/cart/add/P1234") .session(session)) .andExpect(status().is(200)); //Act this.mockMvc.perform(get("/rest/cart/"+ session.getId()).session(session)) .andExpect(status().isOk()) .andExpect(jsonPath("$.cartItems[0]. product.productId").value("P1234")); } }
CartRestControllerTest
and choose Run As | JUnit Test. You will be able to see that the test cases are being executed, and you will be able to see the test results in the JUnit window.While testing REST Controllers, we need to ensure that the web response for the given web request contains the expected JSON object. To verify that, we need some specialized APIs to check the format of the JSON object. The json-path-assert
JAR provides such APIs. We added a Maven dependency to the json-path-assert
JAR from steps 1 to 3.
In step 4, we created our CartRestControllerTest
to verify that our CartRestController
class works properly. The CartRestControllerTest
class is very similar to ProductControllerTest
—the only difference is the way we assert the result of a web request. In CartRestControllerTest
, we have one test method to test the read
method of the CartRestController
class.
The read
method of CartRestController
is designed to return a cart
object as a JSON object for the given cart ID. In CartRestControllerTest
, we tested this behavior in the read_method_should_return_correct_cart_Json_object
test method:
@Test public void read_method_should_return_correct_cart_Json_object () throws Exception { //Arrange this.mockMvc.perform(put("/rest/cart/add/P1234").session(session)) .andExpect(status().is(200)); //Act this.mockMvc.perform(get("/rest/cart/"+ session.getId()).session(session)) .andExpect(status().isOk()) .andExpect(jsonPath("$.cartItems[0].product.productId").value("P1234")); }
In order to get a cart object for the given cart ID, we need to store the cart object in our cart repository first, through a web request. That is what we did in the Arrange
part of the previous test method. The first web request we fired in the Arrange
part adds a product
domain object in the cart, whose ID is the same as the session ID.
In the Act
part of the test case, we simply fired another REST based web request to get the cart object as JSON object. Remember we used the session ID as our cart ID to store our cart object, so while retrieving it, we need to provide the same session ID in the request URL. For this, we can use the mock session object given by the Spring Test framework. You can see that we auto-wired the session object in our CartRestControllerTest
class:
this.mockMvc.perform(get("/rest/cart/"+ session.getId()).session(session)) .andExpect(status().isOk()) .andExpect( jsonPath("$.cartItems[0].product.productId").value("P1234"));
After we get the cart domain object as the JSON object, we have to verify whether it contains the correct product. We can do that with the help of the jsonPath
method of MockMvcResultMatchers
, as specified in the previous code snippets. After sending the REST web request to go get the cart
object, we verified that the response status is okay and we also verified that the JSON object contains a product with the ID P1234
.
Finally, when we run this test case, you can see the test cases being executed, and you can see the test results in the JUnit window.
It's good that we tested and verified the read
method of CartRestController
, but we have not tested the other methods of CartRestController
. You can add tests for the other methods of CartRestController
in the CartRestControllerTest
class to get more familiar with the Spring Test framework.