Monday, 21 May 2012

Sample Spring TTD BDD

Mvn

Command for starting a project

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.geethsangam.music -DartifactId=Geethsangam

cd to the created directory

 mvn compile
 mvn test
 mvn package
 mvn install
 mvn eclipse:clean eclipse:eclipse


To create a sub project

Change the packaging type from jar to pom

  <groupId>com.geethsangam.music</groupId>
  <artifactId>Geethsangam</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>


  <groupId>com.geethsangam.music</groupId>
  <artifactId>Geethsangam</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

Now Mvn for sub modules
(after that do the mvn compile test package install & mvn eclipse:clean eclipse:eclipse)

Domain Layer

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.geethsangam.music -DartifactId=geethsangam-domain

DAO Layer

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.geethsangam.music -DartifactId=geethsangam-dao

Service

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.geethsangam.music -DartifactId=geethsangam-service

Controller

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.geethsangam.music -DartifactId=geethsangam-presentation

WAR Layer

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.geethsangam.music -DartifactId=geethsangam-war


For Cucumber (BDD testing)

Mkdir cucumber

cd cucumber

 (in Cento OS linux use yum command to install)

1) yum install libxml2-devel libxslt-devel git
2) yum --enablerepo=install jruby
3) jruby -S gem install bundler



 (in MacOs use yum command to install) if using port else install mac port

1)  $ sudo port install cmake   $ sudo port install gsl-devel   $ sudo port install libxml2   $ sudo port install qt3 qt3-shlibs qt3mac qt3mac-shlibs
2) Check if Jruby is installed else install it
3) jruby -S gem install bundler

Windows

If you are on a Windows machine (why are you on a Windows machine?), you need to install Cygwin (from its website) with the following packages: subversion, gcc, g++, make, gsl, gsl-devel, cmake, libxml2, libxml2-devel. Although they are not needed for most of the things, you can also install qt3, qt3-devel, bc (the latter is used by some scripts) (http://openrdk.sourceforge.net/index.php?n=Docs.Installation)


== Installing

cd Geethsangam/cucumber
jruby -S bundle install --path=vendor/bundle --binstubs



domain class User.java


package com.geethsangam.music.model;
import java.io.Serializable;
import org.codehaus.jackson.map.JsonMappingException.Reference;

public class User extends Reference implements Serializable {
    private static final long serialVersionUID = 1L;
//    @Id
//    @GeneratedValue
//    @Column(name = "id")
    private long id;
//    @NotEmpty
//    @Size(min = 1, max = 20)
    private String name;
    private String username;
   
    public long getId() {
        return id;
    }
   
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
}


In domain pom.xml add

        <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>persistence-api</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
     <groupId>org.codehaus.jackson</groupId>
     <artifactId>jackson-mapper-asl</artifactId>
     <version>1.4.1</version>
    </dependency>  




package  com.geethsangam.music;

public class UsersDaoTest {

    private UsersDao mockUsersDao;
    private User userMock;
    private List < User > userListMock;

    @Before
    public void prepareMocks() {
       
        mockUsersDao = mock(UsersDao.class);
        userMock = mock(User.class);
        userMock.setId(1L);
        userMock.setName("test");
       
        userListMock = new ArrayList < User >(1);
        userListMock.add(userMock);
    }

    @Test
    public void insertTest() {
        when(mockUsersDao.insert(userMock)).thenReturn(userMock);
        assertEquals(userMock, mockUsersDao.insert(userMock));
    }
}




Service layer test

    private UsersServiceImpl usersService;
    private UsersDao usersDaoMock;
    private User userMock;
    private List < User > userListMock;

    @Before
    public void prepareMocks() {
              
        // set up the DAO mock object
        usersDaoMock = mock(UsersDao.class);

        // Prepare the class to be tested.
        usersService = new UsersServiceImpl();
        usersService.usersDao = usersDaoMock;

        // Set up the mock user
        userMock = mock(User.class);
        userMock.setId(1L);
        userMock.setName("test");
        userMock.setUsername("uSerName");
        when(userMock.getUsername()).thenReturn("uSerName");

        userListMock = new ArrayList < User >(1);
        userListMock.add(userMock);
    }

    @Test
    public void insertTest() {
        when(usersDaoMock.insert(userMock)).thenReturn(userMock);       
        assertEquals(userMock, usersService.insert(userMock));
    }


Service

@Service
public class UsersServiceImpl implements UsersService {

    /**
     * UsersDao.
     */
    @Autowired
     UserDao userDao;
   
    /**
     * Insert User.
     * @param user The user Object.
     * @return Users.
     */
    @Override
    public User insert(User newUser) {

        String newUserName = newUser.getUsername();
        User preExistingUser = fetch(newUserName);
        if (preExistingUser == null) {
            //No users found with this username, so add.
            return usersDao.insert(newUser);
        } else {
            return null;
        }
    }



package com.geethsangam.music.controller;

import java.io.IOException;
import java.io.PrintWriter;

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

import net.sf.json.JSONObject;
import net.sf.json.util.CycleDetectionStrategy;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.servlet.view.json.JsonWriterConfiguratorTemplateRegistry;



/**
 *
 * @author sadhap01
 *
 */
@Controller
public abstract class BaseController {

    /**
     * hex HexUtils.
     */
    protected static HexUtils hex = new HexUtils();

    /**
     * logger Log.
     */
    protected final Log logger = LogFactory.getLog(getClass());
    /**
     * validate Validate.
     */
    protected static Validate validate = new Validate();

    /**
     * keyGen KeyGenerator.
     */
    static KeyGenerator keygen = new KeyGenerator();

    @Autowired
    private UrlKeysService urlKeysService;

    /**
     * Creates JSon Configuration.
     * @param request HttpServletRequest.
     */
    protected void setJSONPreProcessors(HttpServletRequest request) {
        JsonWriterConfiguratorTemplateRegistry registry =
                        JsonWriterConfiguratorTemplateRegistry.load(request);
        PropertyExclusionSojoJsonWriterConfiguratorTemplate template =
                    new PropertyExclusionSojoJsonWriterConfiguratorTemplate();

        //TODO Get the processor to map all Longs and Dates to
        // the relevant config

        template.getJsonConfig().registerJsonValueProcessor("artist_gid",
                                          new StringNullJsonValueProcessor());
        template.getJsonConfig().registerJsonValueProcessor("brand_pid",
                new StringNullJsonValueProcessor());
       template.getJsonConfig().registerJsonValueProcessor("featured_position",
                new StringNullJsonValueProcessor());
        template.getJsonConfig().registerJsonValueProcessor("created_at",
                                          new DateJsonValueProcessor());
        template.getJsonConfig().registerJsonValueProcessor("updated_at",
                                          new DateJsonValueProcessor());
        template.getJsonConfig().registerJsonValueProcessor("promoted_at",
                                          new DateJsonValueProcessor());
        template.getJsonConfig().registerJsonValueProcessor("status",
                                          new IntJsonValueProcessor());

        template.getJsonConfig().setCycleDetectionStrategy(
                                            CycleDetectionStrategy.LENIENT);

        registry.registerConfiguratorTemplate(template);

    }


    @InitBinder
    public void initBinder(HttpServletRequest request, WebDataBinder binder)
                                                            throws Exception {
        setJSONPreProcessors(request);
    }

    public void validationErrors(HttpServletResponse res, JSONObject errs)
                                                           throws IOException {

        PrintWriter out;
        out = new PrintWriter(res.getOutputStream());

        out.println(errs.toString());

        res.setStatus(res.SC_BAD_REQUEST);
        out.close();

    }
protected String generateUrlKey() throws Exception {
    return keygen.getUrlKey(urlKeysService.generate());
}
}



@Controller
public class UsersController extends BaseController {
    /**
     * UsersService Service Object To Call DAO Layer.
     */
    @Autowired
    private UsersService usersService;

    /**
     * Create User.
     * @param modelMap ModelMap.
     * @param body Json String.
     * @param request Request.
     * @param response Response.
     * @return User JSON.
     * @throws JSONException JSONException.
     * @throws Exception Exception.
     */
    @RequestMapping(value = { "/users", "/users/" } ,
                                                   method = RequestMethod.POST)
    public String createUser(ModelMap modelMap, @RequestBody String body,
                      HttpServletRequest request, HttpServletResponse response)
                                              throws JSONException, Exception {
        JSONObject json;
        User user = new User();
        setJSONPreProcessors(request);
        try {
            json = JSONObject.fromObject(body);
            user.setPropertiesFromJSON(json);
            user = usersService.insert(user);
        } catch (JSONException je) {
            response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
        }
        response.setStatus(HttpServletResponse.SC_OK);
        modelMap.put("user", user);
        return "jsonView";
    }



public class UsersControllerTest {

    private UsersController classToTest;

    private String body;

    private String userName;

    HttpServletRequest request;

    HttpServletResponse response;

    @Mock
    ModelMap modelMap;

    @Before
    public void prepareMocks() {

        body = new String("test");

        userName = new String("testuser");

        request = mock(HttpServletRequest.class);

        response = mock(HttpServletResponse.class);

        classToTest = mock(UsersController.class);


    }

    @Test
    public void createUserTest() throws JSONException, Exception {

        when(classToTest.createUser(modelMap, body, request, response)).thenReturn("jsonView");

        assertEquals("jsonView", classToTest.createUser(modelMap, body, request, response));
    }



cuc/support/api_steps.rb

require 'jsonpath'

# TODO: is there a cleaner way of doing this?
class Capybara::Driver::Mechanize < Capybara::Driver::RackTest
  def post_body(url, body, headers = {})
    if remote?(url)
      process_remote_request(:post, url, body, headers)
    else
      register_local_request
      super
    end
  end
 
  def put_body(url, body, headers = {})
    if remote?(url)
      process_remote_request(:put, url, body, headers)
    else
      register_local_request
      super
    end
  end
end

def expand_path(path)
  if path =~ /(:\w+)/
    field = $1
    collection_path = path[0,path.index(field)]
    response = RestClient.get "#{Capybara.app_host}#{collection_path}", :content_type => :json, :accept => :json
    objects = JSON.parse(response.body).values.detect { |o| o.kind_of? Array }
    url_key = objects.last[field.sub(':','')]
    path.sub(field, url_key)
  elsif path =~ /(@\w+)/
    path.gsub(/@\w+/) { |i| instance_variable_get(i) }
  else
    path
  end
end

Given /^I send and accept XML$/ do
  page.driver.header 'Accept', 'text/xml'
  page.driver.header 'Content-Type', 'text/xml'
end

Given /^I send and accept JSON$/ do
  page.driver.header 'Accept', 'application/json'
  page.driver.header 'Content-Type', 'application/json'
end

When /^I authenticate as the user "([^\"]*)" with the password "([^\"]*)"$/ do |user, pass|
  page.driver.authorize(user, pass)
end

When /^I send a GET request (?:for|to) "([^\"]*)"$/ do |path|
  page.driver.get expand_path(path)
end

When /^I send a POST request to "([^\"]*)"$/ do |path|
  page.driver.post expand_path(path)
end

When /^I send a POST request to "([^\"]*)" with the following:$/ do |path, body|
  url = expand_path(path)
  puts url if ENV['DEBUG']
  page.driver.post_body url, body
end

When /^I send a PUT request to "([^\"]*)" with the following:$/ do |path, body|
  page.driver.put_body expand_path(path), body
end

When /^I send a DELETE request to "([^\"]*)"$/ do |path|
  page.driver.delete expand_path(path)
end

Then /^show me the response$/ do
  puts page.driver.response.body
end

Then /^the response status should be "([^\"]*)"$/ do |status|
  if page.respond_to? :should
    page.driver.response.status.should == status.to_i
  else
    assert_equal status.to_i, page.driver.response.status
  end
end

Then /^the response should be empty$/ do
  page.driver.response.body.should be_empty
end

Then /^the JSON response should have "([^\"]*)" with the text "([^\"]*)"$/ do |json_path, text|
  json    = JSON.parse(page.driver.response.body)
  results = JsonPath.new(json_path).on(json).to_a.map(&:to_s)
  if page.respond_to? :should
    results.should include(text)
  else
    assert results.include?(text)
  end
end

Then /^the JSON response should have "([^"]*)" as nil$/ do |json_path|
  json    = JSON.parse(page.driver.response.body)
  results = JsonPath.new(json_path).on(json).to_a
  results.should == [nil]
end

Then /^the JSON response should not have "([^\"]*)" with the text "([^\"]*)"$/ do |json_path, text|
  json    = JSON.parse(page.driver.response.body)
  results = JsonPath.new(json_path).on(json).to_a.map(&:to_s)
  if page.respond_to? :should
    results.should_not include(text)
  else
    assert !results.include?(text)
  end
end

Then /^the JSON response should have the following:/ do |table|
  json    = JSON.parse(page.driver.response.body)
  table.rows.each do |json_path, text|
    results = JsonPath.new(json_path).on(json).to_a.map(&:to_s)
    if page.respond_to? :should
      results.should include(text)
    else
      assert results.include?(text)
    end
  end
end

Then /^the JSON response should not have the following:/ do |table|
  json    = JSON.parse(page.driver.response.body)
  table.rows.each do |json_path, text|
    results = JsonPath.new(json_path).on(json).to_a.map(&:to_s)
    if page.respond_to? :should
      results.should_not include(text)
    else
      assert !results.include?(text)
    end
  end
end

Then /^the JSON response should have (\d+) "([^"]*)"$/ do |count, json_path|
  json    = JSON.parse(page.driver.response.body)
  results = JsonPath.new(json_path).on(json)
  results.first.size.should == count.to_i
end

Then /^the XML response should have "([^\"]*)" with the text "([^\"]*)"$/ do |xpath, text|
  parsed_response = Nokogiri::XML(response.body)
  elements = parsed_response.xpath(xpath)
  if page.respond_to? :should
    elements.should_not be_empty, "could not find #{xpath} in:\n#{response.body}"
    elements.find { |e| e.text == text }.should_not be_nil, "found elements but could not find #{text} in:\n#{elements.inspect}"
  else
    assert !elements.empty?, "could not find #{xpath} in:\n#{response.body}"
    assert elements.find { |e| e.text == text }, "found elements but could not find #{text} in:\n#{elements.inspect}"
  end   
end


users.feature




Feature: Users
  As an API client
  In order to do things with users
  I want to test CRUD for users
 
  Background:
    Given I send and accept JSON
 
  Scenario: List users when there are no users
    When I send a GET request for "/users"
    Then the response status should be "200"
    And the JSON response should have 0 "$.users.*"
 
  Scenario: Create a user
    When I send a POST request to "/users" with the following:
      """
      {
          "user": {
              "artist_gid": null,
              "id": 5885690,
              "is_guide": 1,
              "is_superuser": 0,
              "medium_synopsis": "Sequi quibusdam recusandae enim alias iste quisquam quia. In nam facilis minima iusto. Est soluta odio voluptas. Voluptas impedit aut quos molestiae provident officiis quia similique.Ab ipsum doloribus qui nesciunt illum minima quia magni. Sint qui dicta iste a non. Natus in ea ex. Aspernatur reiciendis molestiae iure cumque repudiandae aut itaque earum.Quo quo ut et eaque. Itaque aliquam fugit qui. Omnis dicta inventore odio distinctio odit autem.",
              "name": "Marquis Kertzmann",
              "short_synopsis": "Voluptatibus minus quia vel.",
              "status": 1,
              "username": "marquiskertzmann"
          }
      }
      """
    Then the response status should be "200"
    And show me the response
    And the JSON response should have the following:
      | jsonpath              | value                        |
      | $.user.username       | marquiskertzmann             |
      | $.user.name           | Marquis Kertzmann            |
      | $.user.short_synopsis | Voluptatibus minus quia vel. |
      | $.user.is_guide       | 1                            |
      | $.user.is_superuser   | 0                            |
    And the JSON response should have "$.user.artist_gid" as nil
    And the JSON response should have "$.user.featured_position" as nil
    And the JSON response should have "$.user.brand_pid" as nil
 
  Scenario: Create the same user twice
    Given a user exists
    When I send a POST request to "/users" with the following:
      """
      {
          "user": {
              "id": 5885690,
              "name": "Test User",
              "username": "testuser"
          }
      }
      """
      # Then the response status should be "403" - is this the correct status code?
      When I send a GET request for "/users"
      Then the response status should be "200"
      And the JSON response should have 1 "$.users.*"