Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

I had come up with a Springframework Junit configuration for Spring Batch that worked pretty well. here. This worked pretty well, but I wanted to have the ability to have transactions to rollback my test data for test repeatability. After much tinkering, this is what I have come up with.

Spring Batch Test Maven Dependencies

I added the following dependencies to write the Spring Batch tests.

<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-test</artifactId>
    <version>${spring.batch.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

Spring Batch Test Utilities

Spring Batch provides the JobLauncherTestUtils to make it easier to test jobs. The gist of a job test class is:

JobExecution jobExecution = jobLauncherTestUtils.launchJob();

To test a step, you provide the step name to the lanuchStep method.

JobExecution jobExecution = jobLauncherTestUtils.launchStep("step1")

My spring test context defined the JobLauncherTestUtils.

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

    <import resource="classpath:applicationContext.xml"/>

    <bean id="jobLauncherTestUtils" class="org.springframework.batch.test.JobLauncherTestUtils"/>
</beans>

Job and Step Test

My Spring Junit test class is fairly simple. This is no different than any other Job or Step test class. NOTE: I was not able to get transactions to work for a Job or a Step.

package com.javaninja.batch;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
 * @author norris.shelton
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TestJobAndStep {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
    public void testJob() throws Exception {
        commonAssertions(jobLauncherTestUtils.launchJob());
    }

    @Test
    public void testStep1() throws Exception {
        commonAssertions(jobLauncherTestUtils.launchStep("step1"));
    }

    private void commonAssertions(JobExecution jobExecution) {
        assertNotNull(jobExecution);

        BatchStatus batchStatus = jobExecution.getStatus();
        assertEquals(BatchStatus.COMPLETED, batchStatus);
        assertFalse(batchStatus.isUnsuccessful());

        ExitStatus exitStatus = jobExecution.getExitStatus();
        assertEquals("COMPLETED", exitStatus.getExitCode());
        assertEquals("", exitStatus.getExitDescription());

        List<Throwable> failureExceptions = jobExecution.getFailureExceptions();
        assertNotNull(failureExceptions);
        assertTrue(failureExceptions.isEmpty());
    }
}

Testing Step-scope components

I was able to have more success with transactions when testing Step-scope components like the JPA-related reader and writer. By using the StepScopeTestExecutionListener in combination with TransactionalTestExecutionListener, I was able to get transactions to work correctly.

The JpaPagingItemReader has the following methods that you need to be concerned with:

  • open – opens the output source.
  • read – reads the data.
  • close – closes the entity manager.

The JpaItemWriter provides the write method that handles all of the writing duties, including flushing the data.

The reader and writer test class is:

package com.javaninja.batch;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.database.JpaItemWriter;
import org.springframework.batch.item.database.JpaPagingItemReader;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.batch.test.StepScopeTestExecutionListener;
import org.springframework.batch.test.StepScopeTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.LinkedList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

/**
 * @author norris.shelton
 */
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
                         StepScopeTestExecutionListener.class,
                         TransactionalTestExecutionListener.class})
@Transactional
@ContextConfiguration
public class TestReaderAndWriter {

    @Autowired
    private JpaPagingItemReader<CamAffiliateEntity> itemReader;

    @Autowired
    private JpaItemWriter<CamAffiliateEntity> itemWriter;

    @Test
    public void testReader() {
        StepExecution execution = MetaDataInstanceFactory.createStepExecution();
        int count = 0;
        try {
            count = StepScopeTestUtils.doInStepScope(execution, () -> {
                int numStates = 0;
                itemReader.open(execution.getExecutionContext());
                CamAffiliateEntity camAffiliateEntity;
                try {
                    while ((camAffiliateEntity = itemReader.read()) != null) {
                        assertNotNull(camAffiliateEntity);
                        assertNotNull(camAffiliateEntity.getAffiliateId());
                        assertNotNull(camAffiliateEntity.getName());
                        assertNotNull(camAffiliateEntity.getChannelId());
                        numStates++;
                    }
                } finally {
                    try { itemReader.close(); } catch (ItemStreamException e) { fail(e.toString());
                    }
                }
                return numStates;
            });
        } catch (Exception e) {
            fail(e.toString());
        }
        assertEquals(12, count);
    }

    @Test
    public void testWriter() throws Exception {
        List<CamAffiliateEntity> usStateEntities = new LinkedList<>();
        CamAffiliateEntity usStateEntity;
        for (int i = 0; i < 100; i++) {
            usStateEntity = new CamAffiliateEntity();
            usStateEntity.setAffiliateId(i);
            usStateEntity.setName("TEST-DELETE-" + i);
            usStateEntity.setChannelId(13);  // test
            usStateEntities.add(usStateEntity);
        }

        StepExecution execution = MetaDataInstanceFactory.createStepExecution();
        StepScopeTestUtils.doInStepScope(execution, () -> {
            itemWriter.write(usStateEntities);
            return null;
        });
    }
}

Summary

This summary provided me with the ability to run by Spring Batch JPA project for the reader and writer repeatedly without having test data-related problems.

The entire project used to write this blog is located on GitHub sheltonn / spring-batch-jpa

February 19th, 2016

Posted In: hibernate, Integration Tests, Java, java ninja, Javaninja, JUnit, Spring, Spring Batch, Test Driven Development, Unit Tests

Leave a Comment

As a follow-up to Cucumber Springframework Integration, I needed to get more flexibility to control when Cucumber was ran. With the current configuration, the Cucumber tests ran every time the Maven build ran. This is as designed, but we needed more flexibility. We also needed the ability to run the Cucumber tests, but without building the code again. I wanted to build the code once, then tests it as it was deployed to each environment. I wanted to ensure that the golden build was the only build that I was using. This cuts down on the variables when there are problem. If this code works in DEV, but doesn’t work in the next environment, then it is something different in the environments.

Controlling database connections per environment

We had the problem where we thought that the Cucumber tests were being ran against our integration environment. This didn’t turn out to be true, because the Cucumber tests were using the same datasource as our Junit tests. Those always ran against our development environment. To overcome the datasource issue, I moved from a single datasource to a datasource per environment and using Spring profiles to determine which database connection to use. As example of what I placed in the Springframework configuration is:

    <beans profile="default">
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
              p:driverClassName="com.mysql.jdbc.Driver"
              p:url="jdbc:mysql://dev_dns/my_database?zeroDateTimeBehavior=convertToNull"
              p:username="_username_"
              p:password="_password_"
              p:maxWait="3000"
              p:maxIdle="100"
              p:maxActive="10"/>
    </beans>
    <beans profile="dev">
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
              p:driverClassName="com.mysql.jdbc.Driver"
              p:url="jdbc:mysql://dev_dns/my_database?zeroDateTimeBehavior=convertToNull"
              p:username="_username_"
              p:password="_password_"
              p:maxWait="3000"
              p:maxIdle="100"
              p:maxActive="10"/>
    </beans>
    <beans profile="ite">
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
              p:driverClassName="com.mysql.jdbc.Driver"
              p:url="jdbc:mysql://ite_dns/my_database?zeroDateTimeBehavior=convertToNull"
              p:username="_username_"
              p:password="_password_"
              p:maxWait="3000"
              p:maxIdle="100"
              p:maxActive="10"/>
    </beans>

Activating a Spring profile

Now that you have multiple Spring profiles, you can control which profile is active by passing the following to the JVM running the program. If you don’t specify a profile, then default is assumed.

-Dspring.profiles.active=dev

Moving the Cucumber to it’s own Maven module

Once I had that, I realized that having the Cucumber tests as part of the code module caused a problem. Maven processes the build lifecycle steps in sequence. If I wanted to run the tests, then the code would have to be built. This was exactly what I did not want. To overcome this, I needed to move the Cucumber to it’s own module, then if I ran the tests via Maven, there wouldn’t be any code to compile, because the code would be in another module.

I started with a module named bonus-core. This had all of the business logic and the Junit and Cucumber that was used to tests the business logic. I moved all of the Cucumber specific dependencies, steps and features to a new module named bonus-core-integration. This is what the new module’s pom.xml contained:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>bonus</artifactId>
        <groupId>com.cdi.igs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>bonus-core-integration</artifactId>

    <dependencies>
        <!-- The module containing the code to be tested with Cucumber -->
        <dependency>
            <groupId>com.cdi.igs</groupId>
            <artifactId>bonus-core</artifactId>
            <version>${project.parent.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--Cucumber related-->
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-jvm-deps</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-spring</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

Once this was done, I could run the bonus-code module and execute the Junit tests, but without the Cucumber tests. Since the Cucumber was not in bonus-code-integration, I could now run just the Cucumber tests, but without building.

Parent pom.xml with and without integration tests

This was closer to what I wanted, but it means that If I run mvn test from the parent pom.xml, the Cucumber tests were always ran. I wanted the flexibility to run the main build without the Cucumber tests. I wanted this because we had a previous project that had so many Cucumber tests, that they took 20 minutes to run. We didn’t have this problem in this project, but I didn’t want to have to scramble if that situation developed. I started with the following modules in the parent pom.xml:

<modules>
    <module>bonus-models</module>
    <module>bonus-core</module>
    <module>bonus-services</module>
    <module>bonus-core-integration</module>
</modules>

I wanted the ability to turn off the execution of the integration modules when needed. I came up with the following:

    <profiles>
        <profile>
            <id>bonus-all</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <modules>
                <module>bonus-models</module>
                <module>bonus-core</module>
                <module>bonus-services</module>
                <module>bonus-core-integration</module>
            </modules>
        </profile>
        <profile>
            <id>bonus-no-integration</id>
            <modules>
                <module>bonus-models</module>
                <module>bonus-core</module>
                <module>bonus-services</module>
            </modules>
        </profile>
    </profiles>

This means that if the developer doesn’t do anything, then they will get all of the modules. They can specify the alternative profile to exclude to the integration tests for this project. I named them _project_-all and _project_-no-integration because we also had the case there there was a master pom for this project. If the project was opened from that view, I wanted the ability to turn off the integration for individual projects, not just all of them across all of the projects under the master pom.xml.

The end result

Once all of my changes were completed, I was able to control all of the test execution according to whatever situation I was in at the time.

  • All tests – I could run the Junit tests and the integration tests simply by running mvn clean test from the parent pom.xml
  • Only Junit tests – I could run the Junit tests and skip all the integration tests by running mvn clean test -Pbonus-no-integration from the parent pom.xml
  • Only Integration tests – I could run the integration tests and skip the build and the Junit tests by running mvn clean test from within the bonus-core-integration module instead of the parent module. If this is used, then the code would be retrieved from our local Artifactory.

I have a normal Springframework-enabled project arranged as a Maven project. There were already JUnit tests in the project, along with the requisite Spring configuration.

I needed to add Cucumber tests to the project. I started by adding a cucumber directory to my src/test/java and src/test/resources directories.

I added my feature file in ../src/test/resources/com/cdi/igs/adapter/cucumber/features/Login.feature.

Feature: Login

  @2014w34 @US2422
  Scenario Outline: A lite registered player with email verified should be able submit username and password in the poker client.
  After submission player will see a system message to complete registration and will get re-direct to complete registration step 2 in IGP

    Given my username is "<UserName>"
    And my password is "<Password>"
    And my registration status is "lit"
    And my email verification status is verified as "<email verification status>"
    When I provide my credentials
    Then I should receive the following Poker status "<login result>"
  Examples:
    | UserName | Password   | login result            |email verification status|
    | player1  | Password#1 | AUTH_FAILED_LIT         | 1                       |
  @2014w34 @US2326
  Scenario Outline: A fully registered user should be able to login into the existing poker client. User in examples must be a fully registered user. 0 = SUCCESS, 1 = AUTH_FAILED

    Given my username is "<UserName>"
    And my password is "<Password>"
    And my registration status is "ful"
    When I provide my credentials
    Then I should receive the following Poker status "<Login Result>"
    And I should receive the following demographic data "<First Name>" "<Last Name>" "<Address1>" "<Address2>" "<City>" "<State>" "<Zip>"
    And I should have a third party authentication token

  Examples:
    | UserName | Password   | Login Result | First Name | Last Name | Address1          | Address2 | City     | State | Zip   |
    | player2  | Password#1 | SUCCESS      | Player     | Test      | 1505 Test Address | Apt 100  | New York | NY    | 80000 |


  @2014w34 @US2538
  Scenario Outline: A lite registered user  whose email is not verified will be shown the relevant messaging, and they will be requested to become fully registered
    Given my username is "<UserName>"
    And my password is "<Password>"
    And my registration status is "lit"
    And my email verification status is verified as "<email verification status>"
    When I provide my credentials
    Then I should receive the following Poker status "<login result>"

  Examples:
    | UserName    | Password        |email verification status  | login result             |
    | 8player     | Password!#01    |0                          | AUTH_FAILED_LIT_NO_EMAIL |


  @2014w36 @US2639
  Scenario Outline: Player with existing Password locked status with IGP authentication, cannot login to poker client.

    Given my username is "<UserName>"
    And my password is "<Password>"
    And my person status is "psl"
    When I provide my credentials
    Then I should receive the following Poker status "<login result>"

  Examples:
    | UserName | Password     | login result              |
    | gawky1  | Password#1    | LOCKED_PASSWORD_RESET     |

  @2014w36 @US2629
  Scenario Outline: Player with existing Reset locked status with IGP authentication, cannot login to poker client.

    Given my username is "<UserName>"
    And my password is "<Password>"
    And my person status is "rsl"
    When I provide my credentials
    Then I should receive the following Poker status "<login result>"

  Examples:
    | UserName  | Password      | login result                  |
    | gawky     | Password#1    | LOCKED_CUSTOMER_SERVICE_RESET |

I added my steps file that defines the implementations in …/src/test/java/com/cdi/igs/adapter/cucumber/steps/LoginSteps.java.

package com.cdi.igs.adapter.cucumber.steps;

import com.cdi.igs.adapter.login.LoginRequest;
import com.cdi.igs.adapter.login.LoginResponse;
import com.cdi.igs.adapter.poker.login.PokerLoginService;
import com.cdi.igs.dao.person.Person;
import com.cdi.igs.dao.person.PersonRepository;
import com.cdi.igs.dao.personhasverifiableitem.PersonHasVerifiableItem;
import com.cdi.igs.dao.personhasverifiableitem.PersonHasVerifiableItemRepository;
import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * Login cucumber steps.
 * @author norris.shelton
 */
@ContextConfiguration("classpath:TestCucumber-context.xml")
public class LoginSteps {
    @Autowired
    private PersonRepository        personRepository;
    @Autowired
    private PersonHasVerifiableItemRepository personHasVerifiableItemRepository;
    @Autowired
    private PokerLoginService       pokerLoginService;


    private String     username;
    private String     password;
    private Person     person;
    LoginResponse loginResponse;

    @Given("^my username is "([^"]*)"$")
    public void my_username_is(String username) throws Throwable {
        this.username = username;
    }


    @And("^my password is "([^"]*)"$")
    public void my_password_is(String password) throws Throwable {
        this.password = password;
    }

    @Given("^my registration status is "(.*?)"$")
    public void my_registration_status_is(String registrationStatus) throws Throwable {
        person = personRepository.findByUserName(username);
        assertEquals(registrationStatus, person.getIdRegistrationStatus());
    }



    @Then("^I should receive the following Poker status "(.*?)"$")
    public void i_should_receive_the_following_Poker_status(String responseStatus) throws Throwable {
        assertEquals(responseStatus, loginResponse.getStatus().value());
    }

    @Given("^my email verification status is verified as "(.*?)"$")
    public void my_email_verification_status_is_verified_as(String emailVerificationStatus) throws Throwable {
     List<PersonHasVerifiableItem> personHasVerifiableItemList =
             personHasVerifiableItemRepository.findByIdPersonAndIdVerifiableItemOrderByTimeStampDesc(person.getIdPerson(), "ema");
        if (!personHasVerifiableItemList.isEmpty()) {
            for (PersonHasVerifiableItem emailVerifiableItem : personHasVerifiableItemList) {
                if (emailVerifiableItem.isSuccess()) {
                    assertEquals(person.getIdPerson(),emailVerifiableItem.getIdPerson());
                    break;
                }
            }
        }
    }

    @Given("^my person status is "(.*?)"$")
    public void my_person_status_is(String personStatus) throws Throwable {
        person = personRepository.findByUserName(username);
        assertEquals(personStatus, person.getIdPersonStatus());
    }

    @When("^I provide my credentials$")
    public void I_provide_my_credentials() throws Throwable {
        LoginRequest loginRequest = new LoginRequest();
        loginRequest.setUserId(username);
        loginRequest.setPassword(password);
        loginResponse = pokerLoginService.login(loginRequest);
    }

    @And(
        "^I should receive the following demographic data "([^"]*)" "([^"]*)" "([^"]*)" "([^"]*)" "" +
        "([^"]*)" "([^"]*)" "([^"]*)"$")
    public void I_should_receive_the_following_demographic_data(String firstName,
                                                                String lastName,
                                                                String address1,
                                                                String address2,
                                                                String city,
                                                                String state,
                                                                String zip)
    throws Throwable {
        assertEquals(firstName, StringUtils.trimToEmpty(loginResponse.getFirstName()));
        assertEquals(lastName, StringUtils.trimToEmpty(loginResponse.getLastName()));
        assertEquals(address1, StringUtils.trimToEmpty(loginResponse.getStreetAddress1()));
        assertEquals(address2, StringUtils.trimToEmpty(loginResponse.getStreetAddress2()));
        assertEquals(city, StringUtils.trimToEmpty(loginResponse.getCity()));
        assertEquals(state, StringUtils.trimToEmpty(loginResponse.getIdState()));
        assertEquals(zip, StringUtils.trimToEmpty(loginResponse.getZipCode()));
    }

    @And("^I should have a third party authentication token$")
    public void I_should_have_a_third_party_authentication_token() throws Throwable {
        assertNotNull(StringUtils.trimToNull(loginResponse.getThirdPartyAuthenticationToken()));
    }
}

Now that I have the feature and the steps created, I added in the Cucumber runner in
…/src/test/java/com/cdi/igs/adapter/cucumber/TestCucumber.java. Notice the glue property of @CucumberOptions. That defines directories to scan.

package com.cdi.igs.adapter.cucumber;</code>

import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;

/**
* JUnit test class for cucumber.
* @author norris.shelton
*/
@RunWith(Cucumber.class)
@CucumberOptions(
tags = {},
glue = {"com.cdi.igs.adapter.cucumber", "cucumber.api.spring"})
public class TestCucumber {
}

In the top of the LoginSteps class, there was a ContextConfiguration annotation.

@ContextConfiguration("classpath:TestCucumber-context.xml")

That Spring context config file is located in …/src/test/resources/TestCucumber-context.xml and thee contents of that are as follows:

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

    <import resource="classpath*:TestAdapter-context.xml"/>
</beans>

It merely says use whatever Spring configuration has already been defined for the rest of the project.

November 20th, 2014

Posted In: Cucumber, Integration Tests, Java, JUnit, Spring, TDD, Test Driven Development, Unit Tests

Tags: , , , , , , , , ,

8 Comments

I have a Springframework integrated Junit test. I had seen annotating a Junit test class with @Test(expected = Exception.class) to test for exceptions before. In this case, we needed to test for Exception and the message was made the difference. This can be accomplished by @Rule

    @Rule
    public ExpectedException expectedEx = ExpectedException.none();

    @Test(expected = Exception.class)
    public void testAddNegativeTransaction() throws Exception {
        expectedEx.expect(Exception.class);
        expectedEx.expectMessage("Cannot withdraw more than available balance.");
        transactionService.addNegativeTransaction(new TransactionRequest("1", "acw", 10, 2000.00));
    }

February 5th, 2014

Posted In: Integration Tests, Java, JUnit, Test Driven Development

Tags: , , , , , , , , , ,

Leave a Comment

LinkedIn Auto Publish Powered By : XYZScripts.com