Spring MVC Based Application Test Driven Development Part 1 – Project Setup and First Test

Test Driven Development (TDD) is a software development practice that using tests to drive software development. We start writing a failing test first and then a code makes the test passed and/or re-factor the code and re-run the test again to ensure the change does not break anything. After that we add another failing test and follow the same steps again until all the tests cover the software requirements.

Spring MVC (Model-View-Controller) is a web application framework developed by SpringSource. It is the leading Java web application framework, open-source and used in many enterprises; big financial and telecommunication companies etc.

In this post, I will show you how to apply TDD in a Spring MVC based application development. Since SpringSource also provides the great IDE for Spring based application development called, Spring Tool Suite (STS), we will use this IDE to write (and run) tests and codes for our Spring MVC based application.

Before proceeding to setup our first project, please ensure these software have installed into your machine.

Setting Up Project

Start the STS and go to the File > New > Others. In the dialog, select the Spring Project and click the Next button, then enter a project name into the Project name field and select the Spring MVC Project and click the Next button.

Spring MVC Project Setup

The next dialog will ask for the top-level package name, let’s enter “com.mycompany.ppms” and then click the Finish button. The Spring MVC Project will be created and shown in the Package Explorer panel of the STS.

Update Maven Dependencies

The next thing we need to do is to modify the Spring framework version in the pom.xml file which is located in the root directory of the project to be 3.2.3; double click the pom.xml file and change the version as follows:

<properties>
  <java-version>1.6</java-version>
  <org.springframework-version>3.2.3.RELEASE</org.springframework-version>
  <org.aspectj-version>1.6.10</org.aspectj-version>
  <org.slf4j-version>1.6.6</org.slf4j-version>
</properties>

Also, add the following spring-test dependency into the Test section; this is the Spring Test Framework.

<!-- Test -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <scope>test</scope>
  <version>${org.springframework-version}</version>
</dependency>

And the last one is the json-path dependency need to added to the Test section as well, next to the spring-test dependency.

<dependency>
  <groupId>com.jayway.jsonpath</groupId>
  <artifactId>json-path</artifactId>
  <version>0.8.1</version>
  <scope>test</scope>
</dependency>

Save the change we have done with the pom.xml file and update the project Maven; go to the project in the Package Explorer and right click on the project select the Maven > Update Project… menu as shown below, and then click the OK button in the Update Maven Project dialog.

The Project Maven Update

The Maven will check the dependency in the pom.xml file we changed recently and update its repository for the project accordingly. Once the Maven finishes the update, you should see the spring-test-3.2.3.RELEASE.jar  and json-path-0.8.1.jar in the Maven Dependencies folder of the project in the Package Explorer. Now our project is ready for the first failing test.

Failing Test For First Requirement

Every failing test should be based on a corresponding requirement, test what is expected from that requirement, one requirement might require different tests per scenario. For example, a product search requirement might require two tests; one for an existing product and another for non-existing product.

As the example given above, we will start our first failing test for a product search requirement; sending a query to the application and receiving a result back as JSON format. The failing test is modified from the sample code provided in the Testing section of the Spring framework reference document.

Creating and Running Tests

To create a test in the project, go to the src/test/java folder, under this folder you will see the package named, com.mycompany.ppms. Right click on this package and then select the New > Class. Enter “ProductSearchTest” into the Name field and click the Finish button; the ProductSearchTest will be created and opened in the Edit panel.

The ProductSearchTest will be based on the JUnit framework that contain the setup() method to create a new instance of MockMvc class for every test case and the test methods per test case as the code shown below.

package com.mycompany.ppms;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class ProductSearchTest {

  @Autowired
  private WebApplicationContext wac;

  private MockMvc mockMvc;

  @Before
  public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
  }

  @Test
  public void testSearchProduct() throws Exception {
    String keyword = "Very Nice Shoes";
    this.mockMvc.perform(get("/product/search")
          .param("q", keyword)
          .accept(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content().contentType(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$.name").value(keyword));
  }

}

The code test the “/product/search” page by requesting a product matched the keyword named, “Very Nice Shoes“; get(“/product/search”). We pass the keyword via the q parameter; .param(“q”, keyword). Then we specify that we accept only the content returned from the page as JSON format; .accept(MediaType.APPLICATION_JSON).

There are three expectations for this test, the first one is the HTTP status must be OK (202); status().isOk(). The second is the content must be JSON format; content().contentType(MediaType.APPLICATION_JSON). The last one is the JSON object must contain the name field with its value is “Very Nice Shoes“; jsonPath(“$.name”).value(keyword)).

Copy the above code and past it into the Edit panel of the ProductSearchTest.java, save and right click on the line “public class ProductSearchTest {” to select the Run As > JUnit Test.

The JUnit panel will show the failing test and the exception along with its corresponding logs and stack trace will printed into the Console panel as the figure shown below.

JUnit Panel

INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [com/mycompany/ppms/test-servlet-context.xml]
ERROR: org.springframework.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@1a13047] to prepare test instance [com.mycompany.ppms.ProductSearchTest@168bd8b]
java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:99)
    ...

As you can see in the logs, the framework tried to load the XML bean definitions at (red-highlight) com/mycompany/ppms/test-servlet-context.xml. However, we have not got this file yet thus; the test failed.

First Part of Implementation

Even though the failure is not due to the product search result, let’s treat this failure as if it is part of the product search test. The first part we will implement to make this test passed is the test-servlet-context.xml file.

Go to the src/test/resources folder of the project in the Package Explorer and then right click on it to select the New… > Package. In the New Java Package dialog, enter “com.mycompany.ppms” into the Name field and click the Finish button; we will create the missing xml file in this package.

Create New  Java Package to Store Test XML File

The next step is to right click on the com.mycompany.ppms package we have created to select the New > Spring Bean Configuration File menu, and enter “test-servlet-context.xml” into the File name field and click the Finish button.

Create XML Test File

The test-servlet-context.xml file contains bean definitions used in the application. We will copy the bean definitions for the test from the servlet-context.xml file generated when we create the Spring project. The file is in the src > main > webapp > resources > WEB-INF > spring > appServlet folder, open this file can copy the content to the test-servlet-context.xml and save the change.

The servlet-context.xml Location

Now we have the file required by the test, let’s re-run the test again. In the STS, typically the right side, the JUnit panel will be opened after running the test. We can use this panel to re-run the test by right-clicking the ProductSearchTest in the list and select the Run menu.

The test will fail but not due to the missing test-servlet-context.xml, rather the result returned from retrieving the “/product/search” page with the respective parameters is not as our expectation. The error code 404 returned instead of 200 as you can see in the JUnit panel.

Second Part of Implementation

Because we have not implemented the code for the “/product/search” page yet, the page not found error code 404 returned. Let’s write very simple code to return the JSON object contains the name field with the “Very Nice Shoes”. Create the new class called, ProductSearch in the src/main/java, com.mycompany.ppms package and put the below code into this class.

package com.mycompany.ppms;

import java.io.IOException;
import java.util.Locale;

import javax.servlet.http.HttpServletResponse;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ProductSearch {
  @RequestMapping(value = "/product/search", method = RequestMethod.GET)
  public void productSearch(Locale locale, Model model, HttpServletResponse response) throws IOException {

    response.setContentType(MediaType.APPLICATION_JSON_VALUE);

    response.getWriter().write("{\"name\":\"Very Nice Shoes\"}");
  }
}

The above code is very simple, it has the @Controller annotation to tell Spring MVC when it scans this class that it is a controller which has the productSearch() method as its action. This action is mapped to the “/product/search” path as defined in the @RequestMapping annotation, the value parameter, also this action accepts only the HTTP GET method.

The productSearch() method has three arguments, the local, model and response. Let’s focus on only the response, for the others I will explain them later in further posts. The response is a HttpServletResponse class, this class represent a HTTP response returned to a client who accesses the “/product/search” path.

As our client is the ProductSearchTest we have written previously, we will return the response correspond to our test expectations which are  the status OK (200),  an application/json content type and JSON object contains the name field with the “Very Nice Shoes” value.

Save the code change in the ProductSearch file and re-run the ProductSearchTest again, the test must pass and the JUnit panel shows the green bar as the figure shown below.

The JUnit Panel with the Green Bar

That’s all for our first failing test and the implementation. You might wonder how the code will handle other test cases, the query is changed to be “Soft Shoes” for example, because now our code set the response to only “Very Nice Shoes”. That means regardless different queries, the implementation always returns the same result.

Further Steps

Did you remember what I mentioned at the beginning of this post? Start with a failing test and then an implementation that makes the test passed. Have we done it? Yes, we have. When you add one more test case to the ProductSearchTest, the ProductSearch code might need to be changed to support the new test case also the existing one; this is what, in my opinion, the most powerful idea of TDD, the code keep evolving as more test cases added to our test suite.

We still have one test case left which is when the ProductSearch cannot find any product matches with the query provided. We will continue to write the test for this case in the next post. I hope you enjoy this post and thank you for reading. Have a nice day.

UPDATE: all the source files are shared at this link https://github.com/kkasemos/spring-mvc-tdd

About these ads
Tagged with: , , ,
Posted in Spring MVC Framework, Spring Test Framework, Test Driven Development
One comment on “Spring MVC Based Application Test Driven Development Part 1 – Project Setup and First Test
  1. Zia says:

    Hi Krit, thank you very much for this truly well written article on a complex subject in a simple way. Keep it up!!!
    -Zia

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: