Friday, March 20, 2015

Expose Service Layer via RESTful services in Spring + Unit Test with TDD


Series:






Goal:

·        We are going to build on top of Service Layer (that we covered in the previous post.)

·        We are going to expose Service Layer as RESTful APIs.

·        We are going to use TDD to develop the RESTful service functionality.

·        We are going to use Jackson which returns JSON object in the RESTful response.

 

Steps:

1.      add dependencies to pom.xml

a.      web, mvc, Jackson, selenium, etc.

2.      Amend context.xml to include MVC

3.      Amend web.xml to include RESTful/Servlet mapping, etc.

4.      Create a POJO which exposes the service layer.

a.      Accepts HTTP requests (standard HTTP verbs, POST, PUT, GET, etc.)

b.      Invokes service layer

c.      Returns response as JSON object.

5.      Create a Test class which uses Selenium to fire HTTP request with desired RESTful service with proper Verb (we will demonstrate a GET call.)

6.      Run the Test Suite

Step 1: Add dependencies


For Spring MVC + JSON related stuff + Selenium (the exact packages are mentioned below in Appendex.)

Step 2: Amend context.xml


Ensure that all relevant namespace and xsd references are included:

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd

             http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd

             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd

             http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-4.1.xsd

             http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd

             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

 

Scan the RESTFul web layer package:

       <context:component-scan base-package="com.mycos.dpweb.ws" />

 

Add JSON handler:

       <!-- Configures view for returning JSON to the client -->

       <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">

             <property name="contentType" value="text/plain"/>

       </bean>

       <!-- 

            maps handler methods based on HTTP paths

      -->

       <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">

             <property name="messageConverters">

                    <util:list id="beanList">

                           <ref bean="jsonMessageConverter"/>

                    </util:list>

             </property>

       </bean>

       <!-- 

            Converts JSON to POJO and vice versa 

      -->

       <bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>

       <bean class= "org.springframework.web.servlet.view.InternalResourceViewResolver">

             <property name="prefix" value="/WEB-INF/"/>

       </bean>

Step 3: Amend web.xml


       <!-- Main configuration file for this Spring web application. -->

       <context-param>

             <param-name>contextConfigLocation</param-name>

             <param-value>/WEB-INF/classes/context.xml</param-value>

       </context-param>

       <!-- Loads the Spring web application context using the config file defined

             above. -->

       <listener>

             <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

       </listener>

       <!-- Define the Spring Dispatcher Servlet for the REST services. The 'contextConfiguration'

             param with an empty value means that the Spring Context won't try to load

             a default file called restservices-servlet.xml -->

       <servlet>

             <servlet-name>restservices</servlet-name>

             <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

             <init-param>

                    <param-name>contextConfigLocation</param-name>

                    <param-value></param-value>

             </init-param>

             <load-on-startup>1</load-on-startup>

       </servlet>

       <!-- This Servlet mapping means that this Servlet will handle all incoming

              requests -->

       <servlet-mapping>

             <servlet-name>restservices</servlet-name>

             <url-pattern>/ws/*</url-pattern>

       </servlet-mapping>

Step 4: Create POJO for RESTFul service


1.      Create a package and a Java class

2.      Annotate with @Controller

3.      Inject Service Layer Object

4.      Inject “View”

5.      Inject @RequestMapping wherever is needed.

Please note that for simplicity I have eliminated the detail steps to get to the code. But essentially you start with a blank template method, implement unit test cases, and then start adding flesh to the template.

Code:

@Controller

public class XxxConroller {

       @Autowired

       private XxxService xxxService;

       @Autowired

       private View jsonView_i;

 

       @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)

       public ModelAndView getUserID(@PathVariable("userId") String userId) {

             /* validate user Id parameter */

             if (isEmpty(userId)) {

                    String sMessage = "Error invoking getUserById - Invalid user Id parameter";

                    return createErrorResponse(sMessage);

             }

             try {

                    return new ModelAndView(jsonView_i, DATA_FIELD, this.xxxService.getUser(userId));

             } catch (Exception e) {

                    String sMessage = "Error invoking getAllLocations. [%1$s]";

                    return createErrorResponse(String.format(sMessage, e.toString()));

             }

       }

 

Step 5: Create Test Class.


1.      Create a package and a class.

public class XxxRESTAPITest extends XxxDBUnitTest{ //extend for the DBUnitTest classes created in the previous post

Create a properties file with expected value. Test case is going to use this value to compare with the json object returned by the RESTful API call. (       ClassPathResource locationTblContent = new ClassPathResource("/webtest/webtest.properties");

2.      Handle JSON object.

·        You can store the entire json object (john_expected_data={"data"\:{"userId"\:"john","isxxx"\:"Y"}})

or

·        You can store only the relevant value and parse the JSON object from the invoked RESTful call to assert a success or unsuccessful call.

 

       @Test

       public void testgetUserByIDRESTAPI() throws Exception {

             init();

             this.testHTML("user/john/", this.prop.getProperty("john_expected_data"));

       }

             StringBuilder strBldr = new StringBuilder();

             strBldr.append(prop.get("url"));

             strBldr.append(uri);

        WebDriver driver = new FirefoxDriver();

        driver.get(strBldr.toString());

        log.debug(driver.findElement(By.tagName("body")).getText());

        assertEquals(expectedValue, driver.findElement(By.tagName("body")).getText());

        driver.close();

        driver.quit();

 

Appendix:


Pom.xml (relevant jar   files)

             <dependency>

                    <groupId>org.springframework</groupId>

                    <artifactId>spring-aop</artifactId>

                    <version>${spring.version}</version>

             </dependency>

             <dependency>

                    <groupId>org.springframework</groupId>

                    <artifactId>spring-aspects</artifactId>

                    <version>${spring.version}</version>

             </dependency>

             <dependency>

                    <groupId>org.springframework</groupId>

                    <artifactId>spring-beans</artifactId>

                    <version>${spring.version}</version>

             </dependency>

             <dependency>

                    <groupId>org.springframework</groupId>

                    <artifactId>spring-expression</artifactId>

                    <version>${spring.version}</version>

             </dependency>

             <dependency>

                    <groupId>org.springframework</groupId>

                    <artifactId>spring-web</artifactId>

                    <version>${spring.version}</version>

             </dependency>

             <dependency>

                    <groupId>org.springframework</groupId>

                    <artifactId>spring-webmvc</artifactId>

                    <version>${spring.version}</version>

             </dependency>

 

             <!-- for Spring WEB/VMC/REST -->

             <dependency>

                    <groupId>org.codehaus.jackson</groupId>

                    <artifactId>jackson-mapper-asl</artifactId>

                    <version>${jackson.mapper.version}</version>

             </dependency>

             <dependency>

                    <groupId>com.fasterxml.jackson.core</groupId>

                    <artifactId>jackson-core</artifactId>

                    <version>${jackson.fasterxml.version}</version>

             </dependency>

             <dependency>

                    <groupId>com.fasterxml.jackson.core</groupId>

                    <artifactId>jackson-databind</artifactId>

                    <version>${jackson.fasterxml.version}</version>

             </dependency>

 

             <!-- for Selenium; testing HTTP request/response -->

             <dependency>

                    <groupId>org.seleniumhq.selenium</groupId>

                    <artifactId>selenium-java</artifactId>

                    <version>${selecnium.version}</version>

             </dependency>

             <dependency>

                    <groupId>xml-apis</groupId>

                    <artifactId>xml-apis</artifactId>

                    <version>${xml-api.version}</version>

             </dependency>

 

No comments:

Post a Comment