Integration Testing with WireMock
What is integration testing?
Integration testing is a phase of software testing where each module of software is combined together and tested as a group. The whole aim of this phase is to identify potential issues in the communication between different modules of the software.
In the current Microservices architecture, where each microservice has a very definite set of responsibilities and together with communication with each other they achieve a goal of a system. So, a microservice alone is not operational. For example, In an Online shopping system, an order service will talk to catalogue service for items. Catalogue service, in turn, will call inventory service and according to the response from inventory service order service will talk to payment service. So, what if the catalogue service is still underdeveloped or not available for testing. Should we wait till all the services are ready? Absolutely not.
How can we write test case for a service without the presence of the dependent service? And there comes “Mocking the services”.So, order service can consume the mock of catalog service as if real service, with the use WireMock.
Wiremock
WireMock is a simulator for HTTP-based APIs.It constructs a HTTP server that we could connect to as we would to an actual web service. It provides a library that helps in stubbing and mocking webservices just like we mock business objects,database objects, HTTP objects etc. in unit testing with various available Mock frameworks (Mockito, PowerMock etc.). Let's start and explore how we can use it with spring boot.
Wiremock with Spring boot
Created two services: Employee Service and EmployeeCalculation service(ECS). So, for the calculation of each employee’s salary Employee service will communicate with the EmployeeCalculation service. ECS has the business logic to calculate the salary based on the Employee’s grade.
Maven Dependency :
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
DemoApplication.java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
EmployeeController.java
@RestController
@RequestMapping(path = “/v1”)public class EmployeeController {@Autowired
private EmployeeService employeeService;
private String commonURI = “/v1”;......
......
.......@GetMapping(“/employee/salary/{id}”)
public String getEmployeeSalary(@PathVariable String id) {
return employeeService.getEmployeeSalary(id, commonURI + “/employee/salary/”);
}
}
EmployeeService.java :
public interface EmployeeService { Employee getEmployeeById(String id,String uri);
List<Employee> getAllEmployees();
void createEmployee(Employee employee);
String getEmployeeSalary(String id, String uri);
}
EmployeeServiceImpl.java
@Servicepublic class EmployeeServiceImpl implements EmployeeService {@Autowired
private EmployeeRepository employeeRepository;@Autowired
private RestTemplate restTemplate;private String empServiceURI=”/v1/employee/salary/”;private String empSalaryServiceURL = “http://localhost:8888";.....@Overridepublic String getEmployeeSalary(String id,String uri) { ResponseEntity<String> response = restTemplate.exchange(empSalaryServiceURL+uri+id,HttpMethod.GET,null, String.class); return response.getBody();
}
}……
Employee.java
@Getter
@Setter
@Entity
@JsonIgnoreProperties({“hibernateLazyInitializer”, “handler”})
@JsonInclude(Include.NON_NULL)public class Employee {@Id
private String id;private String firstName;private String lastName;private String salGrade;private String salary;}
EmployeeServiceTest.java
@RunWith(SpringRunner.class)@SpringBootTest@TestPropertySource(properties {“server.baseuri=http://localhost:8888" })public class EmployeeServiceTest {@Autowired
private ResourceLoader resourceLoader = null;@Autowired
private EmployeeServiceImpl empService;private String empId = “1”;@Rulepublic WireMockRule wireMockRule = new WireMockRule(WireMockConfiguration.options().port(8888).httpsPort(9999).notifier(new ConsoleNotifier(true)).extensions(new ResponseTemplateTransformer(true)));@Test
@SneakyThrowspublic void testCalltoEmployeeService() {try {
wireMockRule.stubFor(get(anyUrl()).willReturn(aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(convertJsonToEmployee(resourceLoader.getResource("classpath:EmpResponse.json").getFile()))));String salary = empService.getEmployeeSalary("1","/v1/employees/salary/");assertTrue("20000".equalsIgnoreCase(salary));verify(exactly(1),getRequestedFor(urlPathEqualTo("/v1/employees/salary/1")));} catch (IOException e) {
e.printStackTrace();
}
}@Test(expected = HttpClientErrorException.class)
public void testCalltoEmployeeServiceWithErrorResponse() {try {
wireMockRule.stubFor(get(anyUrl()).willReturn(aResponse().withStatus(convertJsonToString(resourceLoader.getResource("classpath:404.json").getFile())).withHeader("Content-Type", "application/json")));empService.getEmployeeSalary("2","/v1/employees/salary/");verify(exactly(1), getRequestedFor(urlPathEqualTo("/v1/employees/salary/2")));} catch (IOException e) {
e.printStackTrace();
}
}public String convertJsonToEmployee(File file) {try {
ObjectMapper objectMapper = new ObjectMapper();
EmployeeSalary empWithSalary = objectMapper.readValue(file, EmployeeSalary.class);
return empWithSalary.getSalary();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}public Integer convertJsonToString(File file) {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readValue(file,JsonNode.class);
return (Integer)jsonNode.get("status").asInt();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Why WireMock?
WireMock internally provides a layer over Mock environment. If you look closely in EmployeeServiceTest.java, we not providing any mock objects for ResponseEntity,restTemplate and stub like :
When(restTestTemplate.exchange(anyUrl,HTTP.GET,null,String.class).thenReturn(mockedResponseEntityObject).
It automatically handles internally by WireMock and that is the main advantage of using Wiremock in place of any other Object Mocking frameworks.
How integration testing through Wiremock should happen in real world?
What if the request/response of the service’s API which we have mocked is changed. Service deployment will pass in the all environment’s pipeline,but there is no use of this integration testing, then what is solution?
Create a extra service/server XXXMockServer.In our case, EmployeeMockServer, which will have the the updated mock objects of each and every service. Every service will update the the server(EmployeeMockServer) or vise-verse in case of any change in the service APIs. Updation can be triggered through some batch processing or through using any publisher/subscriber model. Other services can either listen to any change in the mock server or either a scheduler can be set for each service which will periodically fetch the latest API changes and create the mock of the service. Refer the below image for the flow.
Wiremock vs Other Mock frameworks(Mockito,PowerMock etc.)
WireMock is basically used in integration testing and it a simulator for HTTP based APIs. Whereas Mockito,PowerMock provides implementation of method/object and used in Unit testing.
Source :
https://github.com/gtl-khushboo/integration-testing-EmployeeService
https://github.com/gtl-khushboo/integration-testing-EmployeeCalService