Spring MVC Based Application Test Driven Development Part 7 – Product Search Form And External RESTful Service Test

In this post, I will show you how we can test an external RESTful service on which our application depends without having to interact with that service directly. However in order to achieve this we have know all returned data formats and URLs of the service we have to deal with.

The Spring Test Framework provides the MockRestServiceServer class to simulate a RESTful service which we can use to write automated unit tests. This class has several methods to set expected request parameters and response data and content type etc.

First Failing Test For External RESTful Service

Assuming that our product search feature will keep tracks of all search requests from visitor by using external RESTful service provided by third party provider. The returned data formats and its corresponding URL are as follows:

URL: http://tokeeptracks.com/searchkeyword

HTTP method: POST

JSON request content format:

{ “api_key”: “mykey”, “api_secret”:”mysecret”, “keyword”:”Nice Shoes”}

JSON response content format:

Success

{ “stauts_code”:0,”status_text”:”success”}

Fail – blank keyword

{ “stauts_code”:1,”status_text”:”blank keyword”}

In a real RESTful service, JSON request and response contents might contain a lot of fields than an example given above however regardless how much a number of fields JSON has, the approach we are going to implement should work for them as well.

Setting Up MockRestServiceServer

Since an external RESTful service is considered as not a component of our core application, we should separate it to a new class rather than calling it directly in our application. We will create a failing test for a new class named, ToKeepTracksServiceTest, as follows:

SpringMVCTDD Part7 ToKeepTracksServiceTest

The first thing we will put into this test class is the mockServer field and the resourceBundle as shown below.

package com.mycompany.ppms.service.external;

import static org.junit.Assert.*;

import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

import org.junit.After;
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.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com/mycompany/ppms/service/external/test-tokeeptracks-service-context.xml")
public class ToKeepTracksServiceTest {

  @Autowired
  private ToKeepTracksService service;

  @Autowired
  private RestTemplate restTemplate;

  private MockRestServiceServer mockServer;

  private ResourceBundle resourceBundle;

  @Before
  public void setUp() throws Exception {
    this.mockServer = MockRestServiceServer.createServer(restTemplate);
    this.resourceBundle = PropertyResourceBundle.getBundle("ToKeepTracksServiceJsons");
  }

  @After
  public void tearDown() throws Exception {
  }

  @Test
  public void testSearchKeyword() {
    String keyword = "Nice Shoes";
    String responseBody = this.resourceBundle.getString("testSearchKeyword");

    this.mockServer.expect(requestTo(toKeepTracksService.getUrlOfSearchKeyword()))
      .andExpect(method(HttpMethod.POST))
      .andExpect(jsonPath("$.api_key").value("mykey"))
      .andExpect(jsonPath("$.api_secret").value("mysecret"))
      .andExpect(jsonPath("$.keyword").value(keyword))
      .andRespond(withSuccess(responseBody , MediaType.APPLICATION_JSON));

    String result = toKeepTracksService.searchKeyword(keyword);

    assertEquals(responseBody, result);
  }
}

The resourceBundle use to load a JSON string from a file, the reason that I load a JSON string from file rather than put it directly to the code because a JSON string contains a lot of ” characters, when we put the string to our code we have to escape them with \”. This make a JSON string hard to read and copied to re-use with other applications.

In the test above, we use the MockRestServiceServer instance verify whether the RestTemplate instance sends a correct request to the server and simulate a corresponding response back to the RestTemplate.

The expected request must be sent to the same URL as the return value from the getUrlOfSearchKeyword() method, a HTTP method must be POST, and a  JSON string must contain the api_key, api_secret and keyword fields with the “mykey”, “mysecret” and keyword value respectively.

Now we have the test, next we need to create the three missing things, ToKeepTracksService class, and the two files, ToKeepTracksServiceJsons.properties and test-tokeeptracks-service-context.xml.

Create the ToKeepTracksService.java file in the src/main/java/com/mycompany/ppms/service/external and put the following lines into it.

package com.mycompany.ppms.service.external;
import org.springframework.stereotype.Service;

public class ToKeepTracksService {

  public String getUrlOfSearchKeyword() {
    return null;
  }

  public String searchKeyword(String keyword) {
    return null;
  }
}

Create the ToKeepTracksServiceJsons.properties file as follows:

SpringMVCTDD Part7 ToKeepTracksJsons.properties

Put the following content into the file.

testSearchKeyword={"stauts_code":0,"status_text":"success"}

Lastly create the test-tokeeptracks-service-context.xml file as follows.

SpringMVCTDD Part7 test-tokeeptracks-service-context.xml

Put the below content into the file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
</bean>
  <bean id="toKeepTracksService" class="com.mycompany.ppms.service.external.ToKeepTracksService">
  </bean>

</beans>

The bean configuration, the restTemplate and service, will be used in an instance injection for corresponding fields in the ToKeepTracksServiceTest class. Save all the changes we have made to these files and try running the testSearchKeyword() as JUnit test, the test will fail as expected because we have no implementation to make the test passed yet.

Production Code for The ToKeepTracksService

Let’s start with the getUrlOfSearchKeyword() method first, the simplest one. To make it configurable, we will add the setter method as follows:

@Service("toKeepTracksService")
public class ToKeepTracksService {

  private String urlOfSearchKeyword;
  RestOperations restTemplate;

  public void setUrlOfSearchKeyword(String urlOfSearchKeyword) {
    this.urlOfSearchKeyword = urlOfSearchKeyword;
  }
  public String getUrlOfSearchKeyword() {
    return urlOfSearchKeyword;
  }

  public RestOperations getRestTemplate() {
    return restTemplate;
  }

  public void setRestTemplate(RestOperations restTemplate) {
    this.restTemplate = restTemplate;
  }

...
}

How we can configure this URL, via the test-tokeeptracks-service-context.xml as shown below.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"...>

<bean id="toKeepTracksService" class="com.mycompany.ppms.service.external.ToKeepTracksService">
  <property name="urlOfSearchKeyword" value="http://www.tokeeptracks.com/searchkeyword"></property>
  <property name="restTemplate" ref="restTemplate"></property>
</bean>
...
</beans>

Now we have the URL to the service. Let’s add the code makes the test passed into the ToKeepTracksService as follows:

public String searchKeyword(String keyword) {
  String jsonRequest = String.format("{\"api_key\":\"mykey\",\"api_secret\":\"mysecret\",\"keyword\":\"%s\"}",keyword);
  return restTemplate.postForObject(urlOfSearchKeyword, jsonRequest, String.class);
}

This code uses the RestTemplate instance, restTemplate, to send a REST request to the URL defined in the urlOfSearchKeyword field. The request body is a JSON string which its value is as in the jsonRequest variable and we expect the response is a String.class type. Save the change we have made to the ToKeepTracksService and try to re-run the test again, the test will pass.

Second Failing Test for Failure Scenario

Next we will add a new failing test for the scenario that we send a blank keyword to the service as follows:

@Test
public void testSearchKeywordBlankKeyword() {
  String keyword = "";
  String responseBody = this.resourceBundle.getString("testSearchKeywordBlankKeyword");

  this.mockServer.expect(requestTo(service.getUrlOfSearchKeyword()))
    .andExpect(method(HttpMethod.POST))
    .andExpect(jsonPath("$.api_key").value("mykey"))
    .andExpect(jsonPath("$.api_secret").value("mysecret"))
    .andExpect(jsonPath("$.keyword").value(keyword))
    .andRespond(withSuccess(responseBody , MediaType.APPLICATION_JSON));

    String result = service.searchKeyword(keyword);

    assertEquals(responseBody, result);
}

As you can see in the code above, we refer to the testSearchKeywordBlankKeyword property we have to add it to the ToKeepTracksServiceJson.properties as shown below.

testSearchKeywordBlankKeyword={"stauts_code":1,"status_text":"blank keyword"}

Run the test, it will pass because our implementation supports this test case.

Third Failing Test for HTTP Status Internal Server Error

What if we would like to simulate an HTTP error status, Internal Server Error (500) or No Content Found (404) for example. We can simulate this kind of scenario as well by using the MockRestServiceServer. The test for the Internal Server Error is as shown below.

...
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
...

@Test
public void testSearchKeywordServerError() {
  String keyword = "";

  this.mockServer.expect(requestTo(service.getUrlOfSearchKeyword()))
   .andExpect(method(HttpMethod.POST))
   .andExpect(jsonPath("$.api_key").value("mykey"))
   .andExpect(jsonPath("$.api_secret").value("mysecret"))
   .andExpect(jsonPath("$.keyword").value(keyword))
   .andRespond(withServerError());

  try {
    String result = service.searchKeyword(keyword);
  } catch (ToKeepTracksServiceException e) {
    assertEquals("server error", e.getMessage());
  }
}

As you can see in the code above, we use the withServerError() to simulate the error and our test expect the ToKeepTracksServiceException thrown with the “server error” message in this scenario.

Let’s create the ToKeepTracksServiceException class in the src/main/java/com/mycompany/ppms/service/external folder as follows:

package com.mycompany.ppms.service.external;

public class ToKeepTracksServiceException extends Exception {
  public ToKeepTracksServiceException(String message) {
    super(message);
  }
}

Also, we have to change the signature of the ToKeepTracksService.searchKeyword() method to have the throws statement.

public String searchKeyword(String keyword) throws ToKeepTracksServiceException {
...
}

Try to run the test, it will fail because no implementation to throw the exception yet; the test fails because the HttpServerErrorException thrown. The implementation for this scenario will be:

public String searchKeyword(String keyword) throws ToKeepTracksServiceException {

  String jsonRequest = String.format("{\"api_key\":\"mykey\",\"api_secret\":\"mysecret\",\"keyword\":\"%s\"}",keyword);
  String jsonResponse = null;

  try {
    jsonResponse = restTemplate.postForObject(urlOfSearchKeyword, jsonRequest, String.class);  
  } catch(HttpServerErrorException e) {
    throw new ToKeepTracksServiceException("server error");
  }
  return jsonResponse;
}

When we re-run the test this time, it will pass as expected. Now it is time to integrate the ToKeepTracksService to the product search form, open the ProductSearch.java and put the following code into it.

@Controller
public class ProductSearch {

  @Autowired
  ProductService productService;

  @Autowired
  ToKeepTracksService toKeepTracksService;

  @ResponseBody
  public Map<String, Object> productSearch(@RequestParam("q") String q) throws IOException {
  ...
    try {
      toKeepTracksService.searchKeyword(keyword);
    } catch (ToKeepTracksServiceException e) {
      logger.error("searchKeyword error {}", e.getMessage());
    }
    return jsonMap;
  }
  ...
}

Also, we have to add the bean definition for the toKeepTracksService field into the root-context.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
...
  <bean id="toKeepTracksService" class="com.mycompany.ppms.service.external.ToKeepTracksService">
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="urlOfSearchKeyword" value="http://www.tokeeptracks.com/searchkeyword"></property>
    <property name="restTemplate" ref="restTemplate"></property>
  </bean>
  </bean>
</beans>

Save the change and start the Tomcat server to test with the real service. This time when we send a search request to our RESTFul service, it will send a request to store a search keyword to the real service. That all for how to test an external RESTFul service used by Spring MVC based application.

However, the change we have made will cause the ProductSearchTest fails because the ProductSearch class now depends on the ToKeepTracksService which will not available when we run the tests.  In the next post, I will show you how to use Mockito (the popular mock framework) to isolate tests among the classes in our application. The source code for the part 7 can be found at this link https://github.com/kkasemos/spring-mvc-tdd/tree/part7.

 

Posted in Spring MVC Framework, Spring Test Framework, Test Driven Development

Spring MVC Based Application Test Driven Development Part 6 – Product Search Form Integration Test With Tomcat 7 Maven Plugin

In the previous post, Spring MVC Based Application Test Driven Development Part 5 – Product Search Form, we have done an integration test for the product search form however the test requires that a web server to be started before running the test. The web server we use in the previous post is the default one provided by the STS; VMware vFabric tc Server Developer.

So far I have not known how to automatically start, deploy and shutdown this server after running the integration test therefore; I use the Tomcat 7 Maven plugin instead. With this plugin, we can configure the Manven to start the Tomcat 7 embedded version, deploy our application, run the test and shutdown the server after the test finishes.

Setting up Tomcat 7 Maven Plugin

How to set up the plugin, I got the steps from this site, http://cupofjava.de/blog/2013/02/05/integration-tests-with-maven-and-tomcat/

Open the pom.xml file of our project and add configuration for the Tomcat 7 plugin as follows:

<!-- For front-end testing -->
<plugin>
  <groupId>org.apache.tomcat.maven</groupId>
  <artifactId>tomcat7-maven-plugin</artifactId>
  <version>2.1</version>
  <executions>
    <execution>
      <id>start-tomcat</id>
      <phase>pre-integration-test</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <fork>true</fork>
      </configuration>
    </execution>
    <execution>
      <id>stop-tomcat</id>
      <phase>post-integration-test</phase>
      <goals>
        <goal>shutdown</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <uriEncoding>UTF-8</uriEncoding>
  </configuration>
</plugin>

The configuration instructs the Maven to run the plugin in the pre-integration-test and post-integration-test phase respectively. For the first one, we want to start up an Tomcat 7 instance and the latter we want to shutdown the instance.

Also, configure the Tomcat 7 to encode an URI by using UTF-8; this is required if you use parts of URI as request parameter. This will ensure non-English characters get parsed from the URI encoded correctly.

Beside the configuration for the Tomcat 7 plugin, we have to configure the Surefire and Failsafe to run only unit tests and integration tests respectively. The configuration is modified from the example in the http://cupofjava.de/blog/2013/02/05/integration-tests-with-maven-and-tomcat/ as well (change to the higher version) as follows:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.16</version>
  <configuration>
    <excludes>
      <exclude>**/*IntegrationTest*</exclude>
    </excludes>
  </configuration>
</plugin>
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>2.16</version>
  <configuration>
    <includes>
      <include>**/*IntegrationTest*</include>
    </includes>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
    </execution>
  </executions>
</plugin>

As you can see in the configuration above, the Surefire will exclude classes which their name contains “IntegrationTest” when it runs the test. On the other hand, the Failsafe will run only those excluded classes.

We use the Failsafe for the integration test because it will not stop running when encountering test failures whereas the Surefire does. The integration test will be executed when we run the Maven with the verify or integration-test goal.

Next, we have to add the Maven Run Configuration in the STS as the steps in the figure shown below.

SpringMVCTDD Maven Run Configuration

Note that if you are going to run the Tomcat 7 plugin to test a web application that read test data from files and want to specify particular file encoding to match with those files, you might need to setup the STS to use an external Maven runtime instead (as the 5th box in the figure) rather than the embedded version shipped with the STS. I encountered a problem that the embedded version ignored the -Dfile.encoding parameter and causes contents read from files encoded incorrectly thus; tests kept failing.

After we finish setup the Maven Run Configuration for the Tomcat 7 plugin, try to run this maven build and then you will see in the Console panel that the Tomcat 7 start up and shutdown during the integration test as the example shown below. The Firefox will be run automatically as well to test our product search form.

[INFO] create webapp with contextPath: /ppms
เธ?.เธข. 21, 2556 3:39:47 เธ?เน?เธญเธ?เน€เธ—เธตเน?เธขเธ? org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-8080"]
เธ?.เธข. 21, 2556 3:39:47 เธ?เน?เธญเธ?เน€เธ—เธตเน?เธขเธ? org.apache.catalina.core.StandardService startInternal
INFO: Starting service Tomcat
เธ?.เธข. 21, 2556 3:39:47 เธ?เน?เธญเธ?เน€เธ—เธตเน?เธขเธ? org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet Engine: Apache Tomcat/7.0.37

That’s all for the integration test with the Tomcat 7 Maven plugin, and you can find the source code for this part at this link: https://github.com/kkasemos/spring-mvc-tdd/tree/part6

In the next post, I will show you how the Spring Test Framework can help us to test our code that depends on an external RETSful service without having to connect to that external service at all by using the RestTemplate and MockRestServiceServer classes.

Tagged with: , , ,
Posted in Application, Spring MVC Framework, Spring Test Framework, Test Driven Development

Spring MVC Based Application Test Driven Development Part 5 – Product Search Form

In the previous post, Spring MVC Based Application Test Driven Development Part 4 – Product Service/Search and Jackson Integration, we integrated the ProductService into the ProductSearch and re-factored our code to use the Jackson to covert a Map instance which contains a status text and a list of Product instances to a JSON string automatically instead of generating by ourselves.

So far we have implemented only the back-end, the ProductSearch controller. In this post, we will start to develop our front-end which is a product search form to send a keyword to our ProductSearch controller and receive corresponding results back and display in a web page.

JSP and Spring MVC Test Framework Support

We will use the JSP as view however it cannot be tested by using the Spring MVC Test Framework according to the following paragraph in the Testing section of the Spring Framework document also, because our product search form sends a product search request to the ProductSearch controller by using AJAX. therefore; we will use the Selenium, the automated web browser, to test our front-end.

Spring MVC Test builds on the familiar "mock" implementations of the Servlet API available in the spring-test module. This allows performing requests and generating responses without the need for running in a Servlet container. For the most part everything should work as it does at runtime with the exception of JSP rendering, which is not available outside a Servlet container. Furthermore, if you are familiar with how the MockHttpServletResponse works, you'll know that forwards and redirects are not actually executed. Instead "forwarded" and "redirected" URLs are saved and can be asserted in tests. This means if you are using JSPs, you can verify the JSP page to which the request was forwarded.

Selenium Dependency

The Selenium WebDriver is what we will use to automate our front-end testing, it provides a lot of features to automate web testing; enter a text to a form, click a button for example.  To use the WebDriver, we need to update the pom.xml file in our project as follows:

<dependency>
  <groupId>org.seleniumhq.selenium</groupId>
  <artifactId>selenium-java</artifactId>
  <version>2.35.0</version>
  <scope>test</scope>
</dependency>

Save the change, and the STS will automatically download the Selenium jar files and update the classpath of the project.

WebDriver Implementation

The WebDriver supports several web browser, Firefox and Chrome for example. We will use the FirefoxDriver implementation even though is quite slow comparing to the HtmlUnitDriver.

According to the Selenium document, the HtmlUnitDriver JavaScript Engine (Rhino) might not produce the same JavaScript behaviors as the others which use a native browser. For more information, please refer to http://www.seleniumhq.org/docs/03_webdriver.jsp#htmlunit-driver.

I encountered an error when running the HtmlUnitDriver against a web page using the JQuery 2.0.3. We will use the JQuery to perform an AJAX request and render results in the page.

First Failing Test For Product Search Form

The WebDriver works like a web browser, we use it to open a web page at specific URL and check whether the page contains what we expected thus; we have to deploy and start our web application before running the WebDriver. This test is considered as an integration test because it tests the application when all components and services of the application integrated together.

So far we have tested our application against only the automated unit tests and the web context configuration loaded from the test context files. If we try to run the application on a server by right-click on the project and select the Run As > Run on Server , we will see an error displayed in the Console panel indicates that the Spring Framework cannot inject the classes annotated with the @Autowired to their dependent classes.

We will update the root-context.xml in the src/main/webapp/resources/WEB-INF/spring as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">

  <!-- Root Context: defines shared resources visible to all other web components -->
  <jdbc:embedded-database id="dataSource" type="H2">
  </jdbc:embedded-database>

  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="packagesToScan" value="com.mycompany.ppms.model"/>
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="jpaProperties">
      <props>
        <prop key="hibernate.dialect">
          org.hibernate.dialect.H2Dialect
        </prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
        <prop key="hibernate.hbm2ddl.import_files">product_test_data.sql</prop>
      </props>
    </property>
  </bean>
</beans>

As you can see in the xml above, it is similar to the one we defined in the test-service-context.xml file. Also, we have to copy the product_test_data.sql from the src/test/resources to src/main/resources so when we build the war file for the project this file will be included as well and used to populate the database when the application starts on the server.

SpringMVCTDD Part5 the product_test_data.sql

Re-start the server and this time it will not encounter the problem anymore and go to http://localhost:8080/ppms/product/search?q=Shoes to check whether the ProductSearch controller returns results correctly as the screenshot shown below.

SpringMVCTDD Part5 Product Search Result

As you can see in the screenshot above, the results returned as expected. I installed the JSONView Chrome plugin that’s why the JSON displayed as tree hierarchy. Next we will write our first failing front-end test by using the FirefoxDriver.  Create a new class, ProductSearchIntegrationTest in the src/test/java folder in the com.mycompany.ppms package, and put the following code into the file.

package com.mycompany.ppms;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;

public class ProductSearchIntegrationTest {

  static WebDriver driver;

  static String appPath;

  @BeforeClass
  public static void setUpOnce() throws Exception {
    driver = new FirefoxDriver(new FirefoxProfile());
    appPath = "http://localhost:8080/ppms";
  }

  @Before
  public void setUp() throws Exception {
  }

  @After
  public void tearDown() throws Exception {
  }

  @AfterClass
  public static void tearDownOnce() throws Exception {
    driver.quit();
  }

  @Test
  public void testProductSearchFormDisplayCorrectly() {

    driver.get(appPath + "/product/searchForm");
    WebElement searchForm = driver.findElement(By.id("searchForm"));

    /* form exist and valid? */
    assertNotNull(searchForm);
    assertEquals("form", searchForm.getTagName());

    WebElement searchInputText = searchForm.findElement(By.id("q"));

    assertNotNull(searchInputText);
    assertEquals("input", searchInputText.getTagName());
    assertEquals("text", searchInputText.getAttribute("type"));

    WebElement searchInputButton = searchForm.findElement(By.id("searchButton"));

    assertNotNull(searchInputButton);
    assertEquals("input", searchInputButton.getTagName());
    assertEquals("button", searchInputButton.getAttribute("type"));
    assertEquals("search", searchInputButton.getAttribute("value"));

    WebElement searchResults = driver.findElement(By.id("searchResults"));

    /* the section to show search results must be empty */
    assertNotNull(searchResults);
    assertEquals("div", searchResults.getTagName());
    assertEquals("", searchResults.getText());

  }
}

As you can see in the code above, the test starts by opening the page at http://localhost:8080/ppms/product/searchForm by using the FirefoxDriver instance created in the setUpOnce() method and then checking whether a form named, searchForm, exists in the page or not.

After that it checks whether the form has an input text named, q, and if a button named, searchButton, exists, also checks if a label of the button is “search” and finally it expects the search results display section, searchResults,  shows nothing when visiting the page at the first time. Run this test as JUnit test and the test will fail because it cannot get the page as expected.

First Front-end Production Code

To make this test passed we have to do two things, enhance the ProductSearch class and add a JSP page for the product/searchForm path as follows:

ProductSearch.java

@Controller
public class ProductSearch {
  ...
  @RequestMapping(value = "/product/searchForm")
  public String productSearchForm() {
    return "product/searchForm";
  }
}

The productSearchForm method is mapped to the /product/searchForm path and it returns the view named, product/searchForm, to the Spring MVC framework; this name will be used to search for its corresponding JSP file in the src/main/webapp/WEB-INF/views. Therefore, we have to create the searchForm.jsp in a sub-folder named, product, in this path.

SpringMVCTDD Part5 searchForm.jsp

This is the view for the product search form page. Its content is as follow:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Product Search</title>
</head>
<body>
  <form id="searchForm">
    <input id="q" type="text"/>
    <input id="searchButton" value="search" type="button">
  </form>
  <div id="searchResults">
  </div>
</body>
</html>

When you run the ProductSearchIntegrationTest this time it will pass because the WebDriver can load the product search form and find all the elements as expected. Next, we will add a new test to enter a keyword, “Shoes”, and click the search button an finally check whether results displayed correctly or not  by using XPath patterns to locate elements in the page.

Second Failing Test

The second test case for product search results is as follow:

@Test
public void testProductSearchFormSearchAndResultsDisplay() {

  driver.get(appPath + "/product/searchForm");

  WebElement searchForm = driver.findElement(By.id("searchForm"));
  WebElement searchInputText = searchForm.findElement(By.id("q"));
  WebElement searchInputButton = searchForm.findElement(By.id("searchButton"));

  searchInputText.sendKeys("Shoes");
  searchInputButton.click();

  WebDriverWait wait = new WebDriverWait(driver, 5);
  wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//div[@id='searchResults']/li[1]")));

  WebElement searchResultsSection = driver.findElement(By.id("searchResults"));

  assertEquals("Very Nice Shoes", searchResultsSection.findElement(By.xpath("li[1]")).getText());
  assertEquals("Cool Shoes", searchResultsSection.findElement(By.xpath("li[2]")).getText());
}

Front-end Production Code Enhancement

We will add an AJAX code to the searchForm.jsp to send a search request to the ProductSearch controller and display results in the searchResults section when we click the search button as follows:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Product Search</title>
<!-- jquery 2.0.3 produce an error when running in the HtmlUnitDriver -->
<script language="JavaScript" src="${pageContext.servletContext.contextPath}/resources/js/jquery-2.0.3.min.js"></script>

</head>
<body>
  <form id="searchForm">
    <input id="q" type="text"/>
    <input id="searchButton" value="search" type="button">
  </form>
  <div id="searchResults">
  </div>
  <script language="JavaScript">

    $(document).ready(
      function() {
        $('#searchButton').click(sendSearchRequest);
      }
    );
    function sendSearchRequest() {
      $.get(
        '${pageContext.servletContext.contextPath}/product/search',
        { q: $('#q').val() },
        function(data) {
          var searchResultsElem = $('#searchResults');

          $.each(data.products, function(index, product) {
            searchResultsElem.append('<li>' + product.name + '</li>');
          });
        },
        "json" // returned data type
    );
}

</script>
</body>
</html>

As the code above, when we click the button, an AJAX request for a product search will be sent to the product/search path which is mapped to the productSearch method of the ProductSearch class that searches for products match a given keyword in the q parameter and returns results as JSON.

The ${pageContext.servletContext.contextPath} is the JSP Expression Language used to get the contextPath of our application. This help us to refer to the right path when we deploy our application on the server as not root web application, http://localhost:8080/ppms for example.

Run the test again and this time everything will work as expected and the JUnit panel shows the green bar. That’s all for the product search form and you can find the source code in the part 5 branch in the spring-mvc-tdd repository at this link, https://github.com/kkasemos/spring-mvc-tdd/tree/part5.

Next Step

As we can see that our integration test requires the server to start before running it; currently we start it manually. In the next post, we will setup our project to be able to start up a Tomcat 7 server, deploy our application, run the integration test and shutdown the server automatically by using a maven plugin for Tomcat 7.

Issues and Solutions

During writing this post, I used the Chrome Developer Tools to debug the JavaScript code, and it reports an error indicate that it requires the map file of the JQuery, Actually, we do not need this map file if we are no going to debug the JQuery. To solve this problem, we can turn off the source map feature in the Chrome by clicking on the Settings icon and un-check the Enable source maps as the screenshot show below.

SpringMVCTDD Part5 Enable source maps setting

References

Tagged with: , , , , , , , , ,
Posted in Spring MVC Framework, Spring Test Framework, Test Driven Development

Spring MVC Based Application Test Driven Development Part 4 – Product Service/Search and Jackson Integration

In the previous post, Spring MVC Based Application Test Driven Development Part 3 – Product Service, we implemented the ProductService class to provide the product search service to the ProductSearch class which is a controller to handle a product search request and return corresponding result as JSON to a user.

Context Hierarchy For Tests

In this post, we will continue on the ProductService by integrating it into the ProductSearch. If we try to re-run the ProductSearchTest again, we will discover the test fails because the Spring Framework scans our package and cannot find the configuration for an EntityManager instance to be injected into the the ProductService. We will modify the ProductSearchTest to have the @ContextHierarchy as follows:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
  @ContextConfiguration("classpath:com/mycompany/ppms/service/test-service-context.xml"),
  @ContextConfiguration("test-servlet-context.xml")
})
public class ProductSearchTest {
...
}

The @ContextHierarchy used to define a set of context configuration files we would like to load for the test. As the above code, we put the additional @ContextConfiguration that load the configuration from the classpath:com/mycompany/ppms/service/test-service-context.xml we created for the ProductServiceTest; this configuration have to be loaded first to make the injection works as expected. If we re-run the ProductSearchTest again the test will pass.

Product Service and Search Integration

Next, open the ProductSearch class, we will change the following lines of the code to use a ProductService instance injected by the Spring Framework instead.

@Controller
public class ProductSearch {
  @Autowired
  ProductService productService;

  @RequestMapping(value = "/product/search", method = RequestMethod.GET)
  public void productSearch(HttpServletRequest request, HttpServletResponse response) throws IOException {
  ...

    /* search the products */
    List<Product> matchedProducts = productService.findByNameContains(keyword);

    /* generate the response */
    StringBuffer jsonBuffer = new StringBuffer();
    if(!matchedProducts.isEmpty()) {
      jsonBuffer.append("{\"status\":\"found\", \"products\": [");

    for(Product product: matchedProducts) {
      String productJson =
        String.format("{\"name\": \"%s\", \"description\":\"%s\"}",
          product.getName(), product.getDescription());
      jsonBuffer.append(productJson + ",");
    }
  ...
  }
}

Save the change and re-run the test again, at this time only the two tests will pass and the others fail as the JUnit panel shown below,

JUnit Panel shows the two failing tests

Because we have not implement the code in the ProductService class to support the other test cases yet, the tests fail. Let’s add a new failing test to the ProductServiceTest as follows:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-service-context.xml")
public class ProductServiceTest {
  ...
  @Test
  public void testFindByNameContainsShoesFoundTwo() {
    String keyword = "Shoes";
    String name1 = "Very Nice Shoes";
    String description1 = "Very Nice Shoes made in Thailand";
    String name2 = "Cool Shoes";
    String description2 = "Cool Shoes made in Japan";
    List<Product> matchedProducts = productService.findByNameContains(keyword);

    assertEquals(2, matchedProducts.size());

    assertEquals(name1, matchedProducts.get(0).getName());
    assertEquals(description1, matchedProducts.get(0).getDescription());

    assertEquals(name2, matchedProducts.get(1).getName());
    assertEquals(description2, matchedProducts.get(1).getDescription());
  }
}

Re-run the ProductServiceTest again, the test will fail because our test data have only one record for the “Very Nice Shoes”. We will add a new record to the product_test_data.sql as follows:

INSERT INTO product(name, description) VALUES ('Very Nice Shoes','Very Nice Shoes made in Thailand')
INSERT INTO product(name, description) VALUES ('Cool Shoes','Cool Shoes made in Japan')

Re-run the ProductServiceTest again, this time it will pass as expected. You might wonder why I did not change the ProductSearch to use the ProductService instance and add the new SQL statement at the same time before re-running the test. As I explained in the first part of this series that we will write a failing test first then a production code that make that failing test passed.

Next, we will add another new failing test to search for products match the “Cool Shoes” and expect only one result returned. Even though the test is similar to the testFindByNameContainsNiceShoesFoundOne, we add this test to double check our implementation always returns one result when a product match a given full name.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-service-context.xml")
public class ProductServiceTest {
  ...
  @Test
  public void testFindByNameContainsCoolShoesFoundOne() {
    String name = "Cool Shoes";
    String description = "Cool Shoes made in Japan";
    List<Product> matchedProducts = productService.findByNameContains(name);

    assertEquals(1, matchedProducts.size());
    assertEquals(name, matchedProducts.get(0).getName());
    assertEquals(description, matchedProducts.get(0).getDescription());
  }
}

The ProductService we implemented will pass the above test because the named query  to search for products in the Product already support this test case.

Product Search Code Re-factoring and Jackson Integration

Now we have quite enough test cases to help us to check if we make any further changes in our ProductSearch code, it must pass the tests. As we can see in the code of the ProductSearch, there are some lines of the code that need to be cleaned up therefore; we will re-factor our code by removing the lines we do not use anymore as follows:

...
@Controller
public class ProductSearch {

  @Autowired
  ProductService productService;

  final Logger logger = LoggerFactory.getLogger(ProductSearch.class);

  /*final static List<String> products = new ArrayList<String>();
   static {
     products.add("{\"name\": \"Very Nice Shoes\", \"description\":\"Very nice shoes made in Thailand.\"}");
     products.add("{\"name\": \"Cool Shoes\", \"description\":\"Cool shoes made in Japan.\"}");
   }*/

  @RequestMapping(value = "/product/search", method = RequestMethod.GET)
  public void productSearch(HttpServletRequest request, HttpServletResponse response) throws IOException {
  ...
  }
}

After that we will change the code used to generate a JSON string from a Product instance, rather than writing the code to generate the string by our self, we will use the Jackson JSON Processor library supported by the Spring Framework. To use the Jackson, we have to update the Maven dependencies in the pom.xml file as follows:

<!-- Object Mapper -->
<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-mapper-asl</artifactId>
  <version>1.9.13</version>
</dependency>

Save the change we made and the Jackson library will be added to our project, then re-factoring our code to use the Jackson as shown below.

...
@Controller
public class ProductSearch {

  @Autowired
  ProductService productService;

  ...

  @RequestMapping(value = "/product/search", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
  @ResponseBody
  public Map<String, Object> productSearch(@RequestParam("q") String q) throws IOException {

    Map<String, Object> jsonMap = new HashMap<String, Object>();

    String keyword = StringUtils.stripToEmpty(q);
    String text = String.format("Could not find any product matches '%s'", keyword);

    logger.info("productSearch called with '" + StringUtils.stripToEmpty(keyword) + "'");

    /* search the products */
    List<Product> matchedProducts = productService.findByNameContains(keyword);

    /* generate the response */
    if(!matchedProducts.isEmpty()) {
      jsonMap.put("status", "found");
      jsonMap.put("products", matchedProducts);
    } else {
      jsonMap.put("status", "not found");
      jsonMap.put("text", text);
    }

    return jsonMap;
  }
}

In the above code change, we added the new parameter, produces, to the @RequestMapping, this parameter used to ensure the actual content type used to generate the response; in our case the content type is “application/json”. We also added the @ResponseBody annotation to the class, with this annotation the return type is written to the response HTTP body, no need to write a JSON string to a HttServletResponse instance directly as we done earlier. Since we use the Jackson, we return a Map instance contains fields and values we would like to have in a JSON string and the Jackson will automatically generate the string from the Map instance for the response body.

Running All Tests

That’s all for the integration between the Product Search and Service, and the Jackson. So far we have written a number of the failing tests and the production code. Typically, we run a set of tests in a test class, ProductSearchTest for example. However, we can run all the tests in our project by using the Maven test by right-click on the project and select the Run As > Maven test; this menu will instruct the Maven to run all the tests and the results will be displayed in the Console panel as shown below.

...
Results :

Tests run: 7, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 21.184s
[INFO] Finished at: Sun Aug 18 14:00:52 ICT 2013
[INFO] Final Memory: 7M/17M
[INFO] ------------------------------------------------------------------------

Further Steps

The full source code for the part 4 can be obtained from this link, https://github.com/kkasemos/spring-mvc-tdd/tree/part4. In the next post, we will start to write a failing test for a web page that provides a keyword text box and a search button; this page will send a product search request to the ProductSearch and receive corresponding result back to display in the web page.

Tagged with: , , ,
Posted in Application, Spring MVC Framework, Spring Test Framework, Test Driven Development

Spring MVC Based Application Test Driven Development Part 3 – Product Service

In the previous post, Spring MVC Based Application Test Driven Development Part 2 – Product Search Code Re-factoring and New Test Cases, we wrote the tests for the Product Search Requirement No. 1 and re-factored the code. However, the implementation searches for products that are stored in the List object; these product details are hard code. In this post, we will change our implementation, the product details in the List object will be populated from records retrieved from a persistence storage.

Java Persistence API and Persistence Units

We will use Java Persistence API (JPA) to persist and retrieve product details from a persistence storage. In JPA, a class that its instances need to be persisted are defined as entity class, typically an entity class represents a table in a database. The entity class will be managed by EntityManager instances in an application.

We use a persistence unit to define a set of entity classes to be managed by EntityManager instances. The persistence unit can be configured by using the deployment descriptor file called, persistence.xml. With the Spring Framework, we have an option to use this file or define the configuration in the *-context.xml file; we will use the test-service-context.xml file.

Since we want to make our tests run as fast as possible and test data must be generated and cleaned up when the tests starts and finishes, we will use an embedded in-memory database; H2 database (http://www.h2database.com/html/main.html) and use Hibernate as JPA provider. To use Hibernate and H2 in our project, we need to add the dependency for it in the pom.xml file as shown below.

<!-- JPA -->
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-jpa</artifactId>
  <version>1.3.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-entitymanager</artifactId>
  <version>4.2.3.Final</version>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <version>1.3.173</version>
</dependency>

Save the change and the STS will automatically download the H2 and Hibernate jar files. If this is not done automatically, right-click on the project and select the Maven > Update Project… menu. After updating, we should have the Hibernate jar files as follows:

The Hibernate jar files

Product Service Test

Before proceeding to configure the test-service-context.xml file, as we have done in the previous posts, start to writing a failing test first. We will create the ProductServiceTest class in the com.mycompany.ppms.service package in the src/test/java folder and then the production code. We will add the productService field to the ProductServiceTest to store the service provides a method to find products by name. The code is as shown below.

package com.mycompany.ppms.service;

import org.junit.After;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-service-context.xml")
public class ProductServiceTest {

  @Autowired
  private ProductService productService; 

  @Before
  public void setUp() throws Exception {
  }

  @After
  public void tearDown() throws Exception {
  }

  @Test
  public void testFindByNameContainsNiceShoesFoundOne() {

  }
}

Would like to share a little tip here, when you add a reference to a class that does not exist in a project, in the Eclipse we can instruct it to open the dialog for creating a new class by moving the cursor to the line of that reference and press Control + 1 button; the hint box will be shown and select the Create Class ‘Product Search’ option to bring up the dialog.

Create the ProductService class in the com.mycompany.ppms.service package in the src/main/java folder. The ProductService class must have the @Service annotation at the class level as shown below.

package com.mycompany.ppms.service;

import org.springframework.stereotype.Service;

@Service
public class ProductService {
  public List<Product> findByNameCotains(String name) {
  }
}

With this annotation the Spring Framework will be able to inject an instance of the ProductService class into the ProductSearchTest (no need to provide its corresponding bean definition in the test-service-context.xml file). After finishing to create the class, go back to the ProductServiceTest and try to run it as JUnit test. The console will display the error as follow:

INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [com/mycompany/ppms/service/test-service-context.xml]
ERROR: org.springframework.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@1bac9d0] to prepare test instance [com.mycompany.ppms.service.ProductServiceTest@178f2b]
java.lang.IllegalStateException: Failed to load ApplicationContext
...

As we did in the part 1, add the missing test-service-context.xml to the src/test/resource folder in the com.mycompany.ppms.service package. The test-service-context.xml will have the following content.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  <context:component-scan base-package="com.mycompany.ppms.service" />
</beans>

Now we have the context file for the ProductServiceTest, try to re-run the test again to make sure it makes the test passed. As you can see the testFindByNameContainsNiceShoesFoundOne() method has not an implementation yet, we will add the following code into the method.

@Test
public void testFindByNameNiceShoesFoundOne() {
  String name = "Very Nice Shoes";
  String description = "Very Nice Shoes  made in Thailand";
  List<Product> matchedProducts = productService.findByNameContains(name);

  assertTrue(matchedProducts.size() == 1);
  assertEquals(name, matchedProducts.get(0).getName());
  assertEquals(description, matchedProducts.get(0).getDescription());
}

The test retrieves products  by name via the ProductService.findByNameContains() method and it expects only one product returned and the product name matches the name and description provided.

What we are missing in the above code is the Product class, let’s create it in the com.mycompany.ppms.model package in the src/main/java folder. The code for the Product class is as follow:

package com.mycompany.ppms.model;

import java.io.Serializable;

public class Product implements Serializable {
  String name;
  String description;

  public Product(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }
}

After finishing to create the Product class, trying to re-run the ProductServiceTest again; the test will be failed because we have not implemented the findByNameContains() method yet. Let’s add a few lines of code to make the test passed first as follows:

public List findByNameContains(String name) {
  Product product = null;

  List<Product> products = new ArrayList<Product>();

  if(name.contains("Very Nice Shoes")) {
    product = new Product("Very Nice Shoes");
    product.setDescription("Very Nice Shoes made in Thailand");			
    products.add(product);			
  }			

  return products;
}

Why I did not query a persistence unit for products at the first place, the reason is with the above very simple code we can have a very fast feedback how the method must behavior when searching for products; the fast feedback loop is one of the most important thing when we do TDD. While more new tests are being added to the test suite, the production code evolves over a period of time.

Try to re-run the ProductServiceTest again, the test must be passed. Next we will write the code that deals with the persistence unit; refer to the above code we got the idea how the persistence query for the product search will be look like. The code to query a persistence unit for products will be in the Product class as follow:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name = "product")
@NamedQueries({
@NamedQuery(name="Product.findByNameContains", query="select p from Product p where p.name LIKE :name")
})
public class Product implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  Long id;
  String name;
  String description;

  public Product() {
  }

  ...
  public Long getId() {
    return id;
  }

  void setId(Long id) {
    this.id = id;
  }
  ...
}

As you can see in the code, we added many annotations to the class level. The first one is the @Entity, this annotation indicate that this class is an entity class which will be managed by an EntityManager. Next we defined the table name the Product class represent by using the @Table annotation with the name of the table as “product”.

The Java Persistence Query (JPQ) for the product search is defined in the @NamedQuery annotation and the name of the query is Product.findByNameContains. We also added the id field to the class and annotated it with the @Id and @GeneratedValue annotations which means this field used as identifier for the Product class and a value of this field will be generated automatically when a new product created in a persistence storage.

Now we have the entity class, Product, with the annotations required for the JPA. Next let’s re-factor the ProductService class to use an EntityManager to query the persistence storage for products as the code shown below.

@Service
public class ProductService {

  @PersistenceContext
  EntityManager entityManager;

  public List<Product> findByNameContains(String name) {

    TypedQuery<Product> namedQuery = entityManager.createNamedQuery("Product.findByNameContains", Product.class);
    namedQuery.setParameter("name", "%" + name + "%");

    List<Product> products = namedQuery.getResultList();

    return products;
  }
}

The findByNameContains() method modified to have a new field named, entityManager which annotated with the @PersistenceContext; the JPA will inject an EntityManager instance to the class when it encounters a field with this annotation. Also, we change the code to retrieve products by using the entityManager instead. As you can see in the code, we creates an instance of the TypedQuery class by using the createNamedQuery() method. We provide the name of the query we have defined in the Product class and the Class object of the Product class to this method.

After that we set a value of the name argument to the name parameter of the TypedQuery instance; this value will replace the :name parameter we defined in the @NamedQuery of the Product class. Since we would like to find products which their name contains the name provided, we put % at the front and end of the name value. Finally, we call the getResultList() to retrieve products correspond to the query.

The last thing we need to do to make the test passed is that the configuration for the persistence unit we are using. With out this configuration, the code we have written so far will not be able to work as expected because we have not associated the EntityManager instance injected by the JPA with any JPA provider yet, also the target database system, H2. and the test data.

We already created the test-service-context.xml in the src/test/java folder in the com.mycompany.ppms.service package, open the file and update it as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">

  <jdbc:embedded-database id="dataSource" type="H2">
  </jdbc:embedded-database>

  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="packagesToScan" value="com.mycompany.ppms.model"/>
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="jpaProperties">
      <props>
        <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
        <prop key="hibernate.hbm2ddl.import_files">product_test_data.sql</prop>
      </props>
    </property>
  </bean>

  <context:component-scan base-package="com.mycompany.ppms.service" />
</beans>

The fist tag, <jdbc:embedded-datatbase id=”dataSource” type=”H2″> used to define the data source for the entityManagerFactory bean which is defined next to it. The <bean id=”entityManagerFactory” …> tag used to define an implementation of the EntityManagerFactory interface to be used in the application which is the LocalContainerEntityManagerFactoryBean class.  This class has a number of properties which we set some of them by using the <property> tag as follows:

  • packageToScan – a name of a package to be scanned for entity classes. Since we put the Product class into the com.mycompany.ppms.model package, we set this property to this package.
  • dataSource – we refer to the dataSource defined previously in the <jdbc:embedded-datatbase> tag.
  • jpaVendorAdapter – an JPA adapter implementation. Since we use the Hibernate Framework, this property is set to the HibernateJpaVendorAdapter class.
  • jpaProperties – specific properties of the HibernateJpaVendorAdapter which are:
    • hibernate.dialect – since we use the H2 database, we set this property to the H2Dialect class.
    • hibernate.show_sql – set this property to true to enable a log for an SQL generated by the Hibernate.
    • hibernate.hbm2ddl.auto – to instruct the Hibernate to create and drop tables when the session is end, we set this property to create-drop.
    • hibernate.hbm2ddl.import_files – when we set the hibernate.hbm2ddl.auto to create or create-drop, we can specify files contains SQL statements to import data to tables after all of them are created automatically by the Hibernate; we store the statements in the product_test_data.sql and this file is in the src/test/resource folder.

The product_test_data.sql file store an SQL INSERT command as follow:

INSERT INTO product(name, description) VALUES ('Very Nice Shoes','Very Nice Shoes made in Thailand')

Now we have all the required artifacts for the test. Try to re-run the test again, the bar in the JUnit panel must be green for this run. That’s all for the Product Service and its test. In the next post, we will see how can we integrate the ProductService with the ProductSearch class. The full source can be obtained from the part3 branch of the spring-mvc-tdd repository, https://github.com/kkasemos/spring-mvc-tdd/tree/part3/.

Tagged with: , , , , ,
Posted in Application, Spring MVC Framework, Spring Test Framework, Test Driven Development

Spring MVC Based Application Test Driven Development Part 2 – Product Search Code Re-factoring and New Test Cases

In the part 1 of this post series, we learned how to set up our first Spring MVC project also wrote the failing test and the production code to support the Product Search functionality.

In this post, we will re-factor our code and continue from what we have left in the part 1, to add a new test case for the Product Search in the case of a given keyword does not match any product in our application. The expectation in this case is the result returned must be an information message, “Could not find any product matches ‘<keyword>’“, where <keyword> is a keyword a user provided.

Code Re-factoring

Typically, we re-factor codes to make it better; run faster or increase its readability etc. The code written in the part 1 can be improved by removing what we does not use from the productSearch() method; the local and model arguments.

If you notice the method, you will see that we have no code for getting the q parameter used as keyword to search a product yet; we will add the code for this. Also, we will add a new code to write a log to a console or file to trace the method call. The code after re-factoring is as follow:

package com.mycompany.ppms;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Controller
public class ProductSearch {

  final Logger logger = LoggerFactory.getLogger(ProductSearch.class);

  @RequestMapping(value = "/product/search", method = RequestMethod.GET)
  public void productSearch(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String keyword = request.getParameter("q");
    logger.info("productSearch called with '" + StringUtils.stripToEmpty(keyword) + "'");

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

The above code has the new argument called, request, this object store HTTP request parameters and other data which can be retrieved via their corresponding methods. We get the q parameter by using the getParameter() method; request.getParameter(“q”). The logger object used to write the method call trace to the log.

Save the code change and re-run the ProductSearchTest again to see if it works properly. If you look into the Console panel, you will see the ProductSearch’s log as the example shown below.

INFO : com.mycompany.ppms.ProductSearch - productSearch called with 'Very Nice Shoes'

New Test Case for Product Search

Next we will add the new test case, let’s open the ProductSearchTest we created for the part 1 and add the following code to it.

@Test
public void testSearchProductByNameNotFound() throws Exception {
  String keyword = "Soft Shoes";
  String infoText = String.format("Could not find any product matches '%s'", keyword);

  this.mockMvc.perform(get("/product/search")
    .param("q", keyword)
    .accept(MediaType.APPLICATION_JSON))
    .andExpect(status().isOk())
    .andExpect(content().contentType(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.infoText").value(infoText));
  }

What this test do is similar to the first test, it requests the “/product/search” page with a given keyword, check if the HTTP status is OK and the content type is JSON format however it expects the information message in the JSON object, the infoText field, returned to it. Save the change we have done in the ProductSearchTest file and re-run the tests again, now only the old test is passed but the new one is failed.

Product Search Enhancement

We have to enhance our code to support what the new test case expects. Open the ProductSearch file and go to the productSearch() method, it must return not only a product name when it found the product but also the information message when it cannot find any product matches the keyword.

The simplest way to do this to check whether the keyword is a “Very Nice Shoes” or not. If it is, return the JSON object with the name field otherwise the JSON object with the infoText field as the code shown below.

@RequestMapping(value = "/product/search", method = RequestMethod.GET)
  public void productSearch(HttpServletRequest request, HttpServletResponse response) throws IOException {
  String keyword = StringUtils.stripToEmpty(request.getParameter("q"));
  String infoText = String.format("Could not find any product matches '%s'", keyword);

  logger.info("productSearch called with '" + StringUtils.stripToEmpty(keyword) + "'");

  response.setContentType(MediaType.APPLICATION_JSON_VALUE);

  if("Very Nice Shoes".equals(keyword)) {
    response.getWriter().write("{\"name\":\"Very Nice Shoes\"}");
  } else {
    response.getWriter().write("{\"infoText\":\"" + infoText + "\"}");
  }
}

After we finished to modify the code, try to run all the tests again; this time we should see the green bar in the JUnit panel. As you see now we hard code the product names rather than store it in a file, database or other kinds of a persistence storage.

I would say our application at this stage is considered as prototype because it cannot be used in the real situation that users search for a product for its detail however our application provide only a product name.  Also, it must support more than one product and we must be able to add more products to the application dynamically, no hard code for the product list.

Evolving Code by Test Cases

In the rest of this post, we will add more new test cases and you will see how these tests drive our application development. Before we start to write more new tests, I will define the requirements for our application features in each section. We will add new tests corresponding to the requirements defined. Also, we might change the tests we have written so far to match the requirements.

Product Search Feature
Requirement No. 1

Product Search must return one or more product details if a given keyword matches product names; these product names contain the keyword. The JSON format is as follow:

{
"status": "found", 
"products":
[
  {
    "name": "name1",
    "description": "description1"
  },
  {
    "name": "name2",
    "description": "description2",
  }
]
}

Test Case No. 1

  • Given the product list has ‘Very Nice Shoes’ and ‘Cool Shoes’ products
  • When search for products by the q parameter as ‘Shoes’
  • Then the two product details returned with the status as ‘found’

The test for this case is as follow:

@Test
public void testSearchProductByNameShoesFoundTwo() throws Exception {
  String keyword = "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("$.status").value("found"))
    .andExpect(jsonPath("$.products[0].name").value("Very Nice Shoes"))
    .andExpect(jsonPath("$.products[1].name").value("Cool Shoes"));
}

Also, we will change the two previous tests to match with the new JSON format as follows:

@Test
public void testSearchProductByNameFound() 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("$.status").value("found"))
    .andExpect(jsonPath("$.products[0].name").value("Very Nice Shoes"));
}

@Test
public void testSearchProductByNameNotFound() throws Exception {
  String keyword = "Soft Shoes";
  String text = String.format("Could not find any product matches '%s'", keyword);

  this.mockMvc.perform(get("/product/search")
    .param("q", keyword)
    .accept(MediaType.APPLICATION_JSON))
    .andExpect(status().isOk())
    .andExpect(content().contentType(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.status").value("not found"))
    .andExpect(jsonPath("$.text").value(text));
}

The code change for the product search will be:

@Controller
public class ProductSearch {
  final Logger logger = LoggerFactory.getLogger(ProductSearch.class);

  final static List<String> products = new ArrayList<String>();
  static {
    products.add("{\"name\": \"Very Nice Shoes\", \"description\":\"Very nice shoes made in Thailand.\"}");
    products.add("{\"name\": \"Cool Shoes\", \"description\":\"Cool shoes made in Japan.\"}");
  }

  @RequestMapping(value = "/product/search", method = RequestMethod.GET)
  public void productSearch(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String keyword = StringUtils.stripToEmpty(request.getParameter("q"));
    String text = String.format("Could not find any product matches '%s'", keyword);

    logger.info("productSearch called with '" + StringUtils.stripToEmpty(keyword) + "'");

    response.setContentType(MediaType.APPLICATION_JSON_VALUE);

    /* search the products */
    List<String> matchedProducts = new ArrayList<String>();
    for(String product: products) {
      if(product.contains(keyword)) {
        matchedProducts.add(product);
      }
    }

    /* generate the response */
    StringBuffer jsonBuffer = new StringBuffer();
    if(!matchedProducts.isEmpty()) {
      jsonBuffer.append("{\"status\":\"found\", \"products\": [");

      for(String product: matchedProducts) {
        jsonBuffer.append(product + ",");
      }
      jsonBuffer.append("]}");
    } else {
      jsonBuffer.append("{\"status\":\"not found\", \"text\":\"" + text + "\"}");
    }

    response.getWriter().write(jsonBuffer.toString());
  }
}

Test Case No. 2

  • Given the product list has ‘Very Nice Shoes’ and ‘Cool Shoes’ products
  • When search for products by the q parameter as ‘Cool Shoes’
  • Then the ‘Cool Shoes’ product detail returned with the status as ‘found’

The test for this case is as follow:

@Test
public void testSearchProductByNameCoolShoesFoundOne() throws Exception {
  String keyword = "Cool 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("$.status").value("found"))
    .andExpect(jsonPath("$.products[1]").doesNotExist())
    .andExpect(jsonPath("$.products[0].name").value("Cool Shoes"));
}

Since the code change we done previously also cover this test case, no additional code change for it. You might notice that we still use the List object to store all the product details.

In the next post, we will change change this to be a persistence storage instead; with the existing tests, we can re-run them to check if any further changes will cause our application not meet the requirements or not. The full source can be cloned from the part2 branch of the Git repository https://github.com/kkasemos/spring-mvc-tdd

Tagged with: , , , , ,
Posted in Spring MVC Framework, Spring Test Framework, Test Driven Development

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

Tagged with: , , ,
Posted in Spring MVC Framework, Spring Test Framework, Test Driven Development
Follow

Get every new post delivered to your Inbox.