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/.

About these ads
Tagged with: , , , , ,
Posted in Application, Spring MVC Framework, Spring Test Framework, Test Driven Development
One comment on “Spring MVC Based Application Test Driven Development Part 3 – Product Service
  1. […] 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 […]

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: