Junit parametrised class - An use case implementation

In previous post we created maven Junit project and understood fundamentals of Junit testing framework. In this post we will see some advanced feature of Junit and discuss an use case and write the sample code for the same.
Consider a situation, we have a method which generate salary slip of 1000 employee of an organization and for generating salary slip it computes total tax payable and take home salary from various component of salary(HRA,TA,DA,etc.). Do we need to write 1000 test method for each employee or just one?. Scary 1000 methods ,No way!!!.
JUnit provide Parameterized class which can be handy in this situation and fit in our use case. parameterized class is a way to instruct JUnit framework to run the test method multiple time using collection of objects formed inside the class by a method annotated by @Parameters. It's vague definition, agreed. Sample code speaks more loudly than raw text.Lets straight away move to create a Java project and understand how to use parameterized class.

Parameterized class

In introductory post of JUnit we created a maven project, here we will create plain java application in eclipse and add junit jar files manually to execute junit test cases.What are the other things we need to execute this use case:
  • We will create a JSON file with employee details(Name, Designation, and various salary components) which will act as our data source. 
  • A model class corresponding to employee details, remember we have to create collection of Objects which will be used by test method. 
Follow the following steps, create Java project and learn how to use parameterized class. Download sample project source code from here.
  1. Open eclipse and create a Java project (Go to File->New->Projects->Java project), fill project name JunitParameterizedClassProject and proceed with default values and complete it. 
  2. Create two class files - Employee.java under package com.devinline.Model and TestEmployeeSalaryComputation.java under com.devinline.testunit. 
  3. Since we are going to run Junit test we need junit jars too, download both jars(junit.jar and hamcrest-core.jar) from here. And similarly we need to add jar for JSON parsing which can be downloaded from here. Download (json-simple-1.1.1.jar).
  4. Add all downloaded jars in java build path.Right click on project -> Build path -> Configure build path. Click on Add External jars and browse to download location and add both jars.
    If you have followed steps correctly, project structure should be like this:
  1. Let's update Employee.java below following code lines. It is plain java class with some attributes related to employee and a list with variable salary component.HashMap would be better choice however for simplicity.
Hide code lines
package com.devinline.Model;

/**
 * 
 * @author nikhil
 */
public class Employee {
 private String employeeName;
 private String employeeId;
 private String designation;
 private SalaryComponents salaryComponent;
 private String takeHomeSalary;
 public String getTakeHomeSalary() {
  return takeHomeSalary;
 }

 public void setTakeHomeSalary(String takeHomeSalary) {
  this.takeHomeSalary = takeHomeSalary;
 }

 public Employee() {

 }

 public Employee(String employeeName, String employeeId, 
  String designation,SalaryComponents salaryComponent) {
  super();
  this.employeeName = employeeName;
  this.employeeId = employeeId;
  this.designation = designation;
  this.salaryComponent = salaryComponent;
 }

 public void setsalaryComponent(SalaryComponents salaryComponent) {
  this.salaryComponent = salaryComponent;
 }

 public String getEmployeeName() {
  return employeeName;
 }

 public void setEmployeeName(String employeeName) {
  this.employeeName = employeeName;
 }

 public String getEmployeeId() {
  return employeeId;
 }

 public void setEmployeeId(String employeeId) {
  this.employeeId = employeeId;
 }

 public String getDesignation() {
  return designation;
 }

 public void setDesignation(String designation) {
  this.designation = designation;
 }

 public SalaryComponents getsalaryComponent() {
  return salaryComponent;
 }

 public static class SalaryComponents {
  private String basicSalary;
  private String hra;
  private String pfDeduction;
  private String spclAllowance;

  public String getBasicSalary() {
   return basicSalary;
  }

  public void setBasicSalary(String basicSalary) {
   this.basicSalary = basicSalary;
  }

  public String getHra() {
   return hra;
  }

  public void setHra(String hra) {
   this.hra = hra;
  }

  public String getPfDeduction() {
   return pfDeduction;
  }

  public void setPfDeduction(String pfDeduction) {
   this.pfDeduction = pfDeduction;
  }

  public String getSpclAllowance() {
   return spclAllowance;
  }

  public void setSpclAllowance(String spclAllowance) {
   this.spclAllowance = spclAllowance;
  }
 }
}
  1. Update input json file corresponding to this employee class. Copy following code and paste in input.json. JSON file contains details of 5 employee and it is mapped as per employee class attributes.If you are not aware of JSON, just assume it is how we write in JSON file.
{"employees": [
    {"employeeName": "Nikhil",
        "Designation": "SSE",
  "empId":"101",
        "salaryComponent": [{"baseSalary": "1000", "hra": "300", 
         "pfDeduct": "200","spclAllowance":"3000"}],
   "takeHomeSal": "4100"
    }, 
 {"employeeName": "Ranjan",
        "Designation": "SE","empId":"102",
        "salaryComponent": [{"baseSalary": "500", "hra": "300", 
         "pfDeduct": "200","spclAllowance":"2000"}],"takeHomeSal": "2600"
    }, 
 {"employeeName": "Niks",
        "Designation": "SSE","empId":"103",
        "salaryComponent": [{"baseSalary": "4000", "hra": "300", 
         "pfDeduct": "500","spclAllowance":"3000"}],"takeHomeSal": "6800"
    }, 
 {"employeeName": "Ritesh",
        "Designation": "Analyst","empId":"301",
        "salaryComponent": [{"baseSalary": "1000", "hra": "300", 
         "pfDeduct": "200","spclAllowance":"3000"}],"takeHomeSal": "4100"
    }, 
 {"employeeName": "Chandan",
        "Designation": "SSE","empId":"007",
        "salaryComponent": [{"baseSalary": "5000", "hra": "600", 
         "pfDeduct": "200","spclAllowance":"4500"}],"takeHomeSal": "9900"
    }, 
    ]
}
  1. Now its time to write actual test methods using parameterized class. Update TestEmployeeSalaryComputation.java with follwowing code lines.
Hide code lines
package com.devinline.testunit;

import static org.junit.Assert.assertEquals;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.devinline.Model.Employee;
import com.devinline.Model.Employee.SalaryComponents;

/**
 * 
 * @author nikhil
 */
@RunWith(Parameterized.class)
public class TestEmployeeSalaryComputation {
 private String employeeName;
 private String employeeId;
 private String designation;
 private SalaryComponents salaryComponent;
 private String takeHomeSalary;

 public TestEmployeeSalaryComputation(String employeeName,
   String employeeId, String designation,
   SalaryComponents salaryComponentList,
   String takeHomeSalary) {
  super();
  this.employeeName = employeeName;
  this.employeeId = employeeId;
  this.designation = designation;
  this.salaryComponent = salaryComponentList;
  this.takeHomeSalary = takeHomeSalary;
 }

// Prepare the input test data
@Parameters
public static Collection<Object[]> data() {
 List<Employee> employeeList = createEmployeeObjects();
 Object[][] data = new Object[employeeList.size()][];
 for (int i = 0; i < employeeList.size(); i++) {
  data[i] = new Object[] { 
  employeeList.get(i).getEmployeeName(),
  employeeList.get(i).getEmployeeId(),
  employeeList.get(i).getDesignation(),
  employeeList.get(i).getsalaryComponent(),
  employeeList.get(i).getTakeHomeSalary() };
 }
 return Arrays.asList(data);
}

// Read input json and create Employee object
private static List<Employee> createEmployeeObjects() {
 List<Employee> testEmployeeObjectList = 
   new LinkedList<Employee>();
 JSONParser parser = new JSONParser();
 ClassLoader classLoader = TestEmployeeSalaryComputation.class
   .getClassLoader();
 File inputFile = new File(classLoader.getResource(
   "resources/input.json").getFile());
try {
 Object obj = parser.parse(new FileReader(inputFile));
 JSONObject inputTestDataObject = (JSONObject) obj;
 JSONArray empList = (JSONArray) inputTestDataObject
   .get("employees");
// create Employee object and add to testInputList.
for (int i = 0; i < empList.size(); i++) {
 Employee emp = new Employee();
 JSONObject temp = (JSONObject) empList.get(i);
 String employeeName =
   (String) temp.get("employeeName");
 String designation = 
   (String) temp.get("Designation");
 String employeeId = 
   (String) temp.get("empId");
 String takeHomeSal =
   (String) temp.get("takeHomeSal");
 JSONArray salComp = (JSONArray) temp.get("salaryComponent");
 // We have only one entry in array
 SalaryComponents salaryComp = 
   getSalCompObject((JSONObject) salComp.get(0));
  if (null != employeeId) {
   emp.setEmployeeId(employeeId);
   emp.setDesignation(designation);
   emp.setEmployeeName(employeeName);
   emp.setTakeHomeSalary(takeHomeSal);
  }
  emp.setsalaryComponent(salaryComp);
  testEmployeeObjectList.add(emp);
  }
 } catch (FileNotFoundException e) {
  Logger.getLogger(TestEmployeeSalaryComputation.
    class.getName())
    .log(Level.SEVERE, null, e);
 } catch (IOException e) {
  Logger.getLogger(TestEmployeeSalaryComputation.
    class.getName())
    .log(Level.SEVERE, null, e);
 } catch (org.json.simple.parser.ParseException ex) {
  Logger.getLogger(TestEmployeeSalaryComputation.
    class.getName())
    .log(Level.SEVERE, null, ex);
 }

 return testEmployeeObjectList;
}

// create saalry Object
static private SalaryComponents getSalCompObject(JSONObject 
     salaryJSONObj) {
 SalaryComponents salObj = new SalaryComponents();
 if (null != salaryJSONObj.get("baseSalary")) {
 salObj.setBasicSalary((String) 
   salaryJSONObj.get("baseSalary"));
 salObj.setHra((String) salaryJSONObj.get("hra"));
 salObj.setPfDeduction((String)
   salaryJSONObj.get("pfDeduct"));
 salObj.setSpclAllowance((String)
   salaryJSONObj.get("spclAllowance"));
 }
 return salObj;
}

@Test
public void salaryComutationTest() {
 Long totalSalary = 
 Long.parseLong(salaryComponent.getBasicSalary(), 10)
 + Long.parseLong(salaryComponent.getSpclAllowance(), 10)
 + Long.parseLong(salaryComponent.getHra(), 10);
 Long takeHomeSal = totalSalary
   - Long.parseLong(salaryComponent.getPfDeduction());
 assertEquals(takeHomeSalary.toString(),
   takeHomeSal.toString());

}
}

Lets walk through code lines of TestEmployeeSalaryComputation.java:
  • This class has been annotated by @RunWith(Parameterized.class), it inform JUnit that attribute of this class will be part of the Object whose collection will be used by test methods.
  • Each Object of TestEmployeeSalaryComputation will be constructed using five argument constructor and the data values in the @Parameters method.
  • Method annotated with  @Parameters will be responsible for creating collection of objects. 
  • Method createEmployeeObjects() parses input json and creates list of employee Objects.
    getResource() method  is used to read files in resource directory.
  • Method salaryComutationTest() annotated with @Test , will be executed by the JUnit and it will use collection of Objects created by data() method.
    It computes takeHomeSalary (totalSalary - pfDeduct) and aeertMethod is used to comapre precomputed value(part of JSON input) with this caculated value. If both are equals then test case passes else error reported.Here all test case will pass. You can change takeHomeSal in jason and verify.
  • Please note you can't compare Long values like this :
    assertEquals(takeHomeSalary, takeHomeSal);We have to append L in each value indicating both values are Long. For simplcity I have changes in string and compared.
  1. Now we are in position to execte run our test project and verify the input. Right click on project -> Run as -> Junit project.
    Boommm!!!! You should see green color indicating all test case passed.If you did not make it do not worry, download source file from here and verify.

Next: Mocking, Junit rule CateogryPrevious: Maven project with Junit - Junit part 1

2 Comments

Previous Post Next Post