Pages

Writing automated unit and integration test cases for Spring boot application and gradle | Approach | case example

I want to write about my experience of a TDD environment where I had a unique problem and how i solved it.

The problem was that i have to develop an application which provides some endpoints to another team to access our teams features. The application would receive some service request, call a bunch of other web services to do some analysis and then return the status of the request (valid, invalid with other details). I have to write unit tests as well as integration tests while developing the application, so that, in the future, if someone else made any code change and break something, the tests should fail. The application was being build and deployed to the servers using a continuous integration pipeline. The problem is that I cannot assume that other services that my service is depending on, are always up, because if they are down, the test would fail and the changes would not deploy in the development servers even if there is nothing wrong with my code.

Solution.
The solution to this kind of problem is to separate your tests to unit tests and integration tests. The unit tests should run every time you are trying to build the application and deploy it to the servers. There should be no external dependencies in the unit tests. For example, external services, external databases, message queues or streams should not be called in the unit test context. In the integration test, you test the integration among different components so they could be unit-integration test or integration among different applications/systems. In those automated tests, you could access the separate components and applications.

These concepts are much better described in the awesome article written by the very awesome Martin Fowler in his blog. Do check that out.

So for my approach to separate out the unit tests and the integration tests, I adopted the following approach.

First create categories on which you want to separate your tests into:

import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@WebMvcTest
public interface IntegrationTest {

}

public interface UnitTest {

}



Then every test case that you want to mark as UnitTest or integrationTest, you can simply apply the category.

import io.restassured.http.ContentType;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import com.rajan.IntegrationTest;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static io.restassured.RestAssured.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

;

@RunWith(SpringRunner.class)
@WebMvcTest
@Category(IntegrationTest.class)
public class ControllerTest {

You will require Junit4 though to use this feature of junit.
Once the tests are categorized, you can write a gradle job to run instances of a particular category as follows.

// add this in the build.gradle
task integrationtest(type: Test){

 environment "context", "inttest"
 useJUnit{
  includeCategories 'com.rajan.IntegrationTest'
 }
}

// to execute this job from command do the following
gradle clean integrationtest

The above command will run all the instances of the tests market by the marker interface IntegrationTest class.

You will have to add the test dependencies in the build.gradle file to do these tasks.

//testCompile( 'com.github.tomakehurst:wiremock:2.6.0' )
 testCompile( 'io.rest-assured:rest-assured:3.0.2' )
 testCompile( 'org.springframework.boot:spring-boot-starter-test' )
 
 testCompile( 'org.powermock:powermock-module-junit4:1.6.2' )
 //testCompile( 'org.powermock:powermock-api-mockito:1.10.8' )
 testCompile( 'org.mockito:mockito-core:1.10.8' )
 testCompile( 'org.easymock:easymock:3.4' )
 testCompile group: 'org.hamcrest', name: 'hamcrest-core', version: '1.3'
        testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'

After this
I would also like to talk a little bit about a few popular libraries out there for writing tests and some popular features of them.

Customizing docker image | case example

Using concourse for ci-cd pipelines.
Concourse internally uses docker images for its jobs.
Jobs basically require some input and output folders and some environment variables to be set, and then the commands to be executed.

Basically docker container is like an instance of virtual machine with the required environments configured so that user can directly use the binaries to execute commands and do the job.
For example, to run a npm command in the docker, you require a docker image with npm binaries already installed and configured. So when you start the container, you can just start executing those commands.

Sometimes, you will not find a docker image with all the dependencies installed and configured into it. So you will have to prepare the environment yourself once.
I encountered a similar situation where most of the things were configured in one of the docker image but some of the additional dependencies which i require were not there.
I did some research and got the work done.

Basically I had to build some old tomcat grid applications in the pipeline and publish the results to quality hub.

I found a docker image with the java, and ant configured but ivy binaries were missing in the image.

NOTE: IVY is the dependency management api which is normally used in Ant projects. Its similar to maven in that you declare dependencies in an xml file and it can download that for you.

So, here are the steps to get the work done.
1. Pull and run the first docker image.

docker pull docker.artifactory.rajan.com/java/ant-image:latest
docker run -itd --name ant-ivy docker.artifactory.rajan.com/java/ant-image sh

Note: if your docker image is in custom artifactory and not in docker hub, you have to give the complete path of the artifactory as shown above.

2. Attach your cmd to the docker container and check

docker attach ant-ivy

3. Clone ivy project into the folder

git clone https://git-wip-us.apache.org/repos/asf/ant-ivy.git
cd ant-ivy
ant jar

4. copy the ivy.jar to /usr/share/ant/lib folder.

5. If you have any other files to copy from your local to running docker container use the following command.

docker cp fileToCopy ant-ivy:/folder_name

6. Once your configuration is complete, create a new image out of the running container.

docker commit ant-ivy docker.artifactory.rajan.com/rajan/ant_with_ivy:version_01

This command will create a new docker image out of the running ant-ivy container instance. (run it from different cmd as exit command from attached cmd will exit the container)

7. Login to the custom docker artifactory

docker login docker.artifactory.rajan.com

8. push the image to artifactory

docker push docker.artifactory.rajan.com/rajan/ant_with_ivy:version_01

Now if you do the following command from another machine, it will be able to get the docker image.

docker pull docker.artifactory.rajan.com/rajan/ant_with_ivy:version_01

That's all for today.
Hope this helps someone.