Showing posts with label junit. Show all posts
Showing posts with label junit. Show all posts

Friday, February 10, 2023

Spring Boot Mockito Example

In this article, we will make a shallow dive into mocking using Mockito. We'll be utilising the code from Spring Boot MockMvc Example. We'll be adding a unit test that makes use of Mockito.

We will not dive into much detail on how to build the project as our focus will be on mocking with Mockito. Why do we need to mock? Mocks are useful if you have a dependency to an external system. Such as a database connection, a third party web API, etc. In order for our unit tests to be quick and simple, we need to mock (fake) the database connection, a file read or whatever that may be.

Please refer to the source blog if you want to create the project yourself. Here is the finished project, clone it here, github.com/jpllosa/tdd-mockmvc. We'll just go straight into creating the additional test with mocks.

Mockito

For this example, we are going to pretend that the service layer is a call to a third party web service. Imagine if the third party web service is down, how can you test your code? To be able to run the unit test, we'll need to mock it. Read through the code below. Pay particular attention to getGcfTestWithMockedService(). It will all be explained below.


package com.blogspot.jpllosa.controller;

import static org.hamcrest.CoreMatchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import com.blogspot.jpllosa.model.GreatestCommonFactor;
import com.blogspot.jpllosa.service.MathFunService;

@SpringBootTest
@AutoConfigureMockMvc
public class MathFunControllerTest {
	
	@Autowired
	private MockMvc mockMvc;
	
	@InjectMocks
	private MathFunController controller;
	
	@Mock
	private MathFunService service;
	
	@Test
	public void getGcfTest() throws Exception {
		this.mockMvc.perform(get("/get-gcf?firstNumber=12&secondNumber=16"))
			.andDo(print())
			.andExpect(status().isOk())
			.andExpect(jsonPath("$.firstNumber", is(12)))
			.andExpect(jsonPath("$.secondNumber", is(16)))
			.andExpect(jsonPath("$.gcf", is(4)));
	}

	@Test
	public void getGcfTestWithMockedService() throws Exception {
		GreatestCommonFactor gcf = new GreatestCommonFactor();
		gcf.setFirstNumber(12);
		gcf.setSecondNumber(16);
		gcf.setGcf(4);
		
		when(service.findGcf(12, 16)).thenReturn(gcf);
		GreatestCommonFactor actual = controller.getGcf(12, 16);
		
		verify(service).findGcf(anyInt(), anyInt());
		
		assertEquals(4, actual.getGcf());
	}
}

First off, we mark our controllor @InjectMocks. This means mock injection is performed here. Then we mark our service @Mock. This is our mocked object which will be injected to the controller. Now that it's all set up, we go to getGcfTestWithMockedService(). The three things we usually do when mocking an object is: do something when it's called (when), return something based on the call (thenReturn), and verify if the method was actually invoked by our code (verify). Before the controller gets hit, we prepare the service on what to do when findGcf is invoked with 12 and 16 as parameters. When it is invoked, we return a greatest common factor object that we also prepared beforehand. After the controller gets hit, we verify that findGcf was actually invoked. Here, we are happy that any integer was passed. We only care that it was invoked, the third party system does the actual finding of the greatest common factor. We should have two passing tests like below:

Spring Boot Mockito Example
Spring Boot Mockito Example

There you have it. A simple example of how to use Mockito.

Wednesday, January 18, 2023

Spring Boot MockMvc Example

This article is an example of Spring Boot's MockMvc. We'll be utilising the code from Test-Driven Development with Spring Boot Starter Test. We'll move our MathFun code to the service layer and create a REST controller that will call it.

We will not dive into much detail on how to build the project as our focus will be on testing the web layer with MockMvc. The beauty of MockMvc is it doesn't start an HTTP server. It handles the incoming HTTP request and hands it off to our controller. Our code is called exactly the same way, just like a real HTTP request. With the power of annotations, Spring's injects MockMvc.

Create the Project

Create your project, head to Spring Initializr. We'll be using Maven, Java 8, Spring Boot 2.7.7, and with developer tools and web (see image) as dependencies. This is what I've got:

Project Metadata

  • Group: com.blogspot.jpllosa
  • Artifact: tdd-mockmvc
  • Name: TDD Web layer
  • Description: Test-Driven Development with MockMvc
  • Package name: com.blogspot.jpllosa

When you are ready, import the project to your IDE. I use Spring Tool Suite 4. If you are keen on the finished project, clone it here, github.com/jpllosa/tdd-mockmvc.

Spring Boot MockMvc Example
Spring Boot MockMvc Example

Create the Model

Create a package named com.blogspot.jpllosa.model and create GreatestCommonFactor. This object will be returned by our controller. Don't worry, Spring will automatically send the response body in JSON format because of the @RestController annotation.


package com.blogspot.jpllosa.model;

public class GreatestCommonFactor {
	Integer firstNumber;
	Integer secondNumber;
	Integer gcf;
	
	public Integer getFirstNumber() {
		return firstNumber;
	}
	
	public void setFirstNumber(Integer firstNumber) {
		this.firstNumber = firstNumber;
	}
	
	public Integer getSecondNumber() {
		return secondNumber;
	}
	
	public void setSecondNumber(Integer secondNumber) {
		this.secondNumber = secondNumber;
	}
	
	public Integer getGcf() {
		return gcf;
	}
	
	public void setGcf(Integer gcf) {
		this.gcf = gcf;
	}

}

Create the Service

Create a package named com.blogspot.jpllosa.service and create MathFunService. This is our service layer, the layer that handles the business logic. The service layer also serves as a traffic cop between the controller and persistence layer. Hence, this bean is annotated with @Service.


package com.blogspot.jpllosa.service;

import org.springframework.stereotype.Service;

import com.blogspot.jpllosa.model.GreatestCommonFactor;

@Service
public class MathFunService {

	public GreatestCommonFactor findGcf(int x, int y) {
		int result = getGCF(x, y);
		
		GreatestCommonFactor gcf = new GreatestCommonFactor();
		gcf.setFirstNumber(x);
		gcf.setSecondNumber(y);
		gcf.setGcf(result);
		
		return gcf;
	}
	
	public int getGCF(int x, int y) {
		return (y == 0) ? x : getGCF(y, x % y);
	}
}

Create the Controller

Last but not the least. Create a package named com.blogspot.jpllosa.controller and create MathFunController. This is our web layer. GET HTTP requests to the resource /get-gcf are routed here. The method checks for two query parameters and are automatically injected to the method parameters. Then sends it to the service layer for processing. It returns the type GreatestCommonFactor which is automatically marshalled into a JSON body as mentioned earlier.


package com.blogspot.jpllosa.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.blogspot.jpllosa.model.GreatestCommonFactor;
import com.blogspot.jpllosa.service.MathFunService;

@RestController
public class MathFunController {
	
	@Autowired
	private MathFunService service;

	@GetMapping("/get-gcf")
	public GreatestCommonFactor getGcf(@RequestParam int firstNumber, @RequestParam int secondNumber) {
		return service.findGcf(firstNumber, secondNumber);
	}
}

Running the App

Now that everything is wired up, we should be able to run the application and see the response. To run the app in STS4, right click, Run As + Spring Boot App.


$ curl -G -d "firstNumber=12" -d "secondNumber=16" http://localhost:8080/get-gcf
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    44    0    44    0     0   1419      0 --:--:-- --:--:-- --:--:--  1419{"firstNumber":12,"secondNumber":16,"gcf":4}

Above, we are finding the GCF of 12 and 16. The GCF is 4.


$ curl "http://localhost:8080/get-gcf?firstNumber=0&secondNumber=6"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    42    0    42    0     0   2800      0 --:--:-- --:--:-- --:--:--  2800{"firstNumber":0,"secondNumber":6,"gcf":6}

Above is another way of sending the curl command, we are finding the GCF of 0 and 6. The GCF is 6. If you don't have curl, you can use the browser or Postman to send your HTTP request.

Spring Boot MockMvc

Now the thing we have been waiting for. Create a package named com.blogspot.jpllosa.controller under src/test/java/ and create MathFunControllerTest. This is the class under test. To run the test in STS4, right click, Run As + JUnit test.


package com.blogspot.jpllosa.controller;

import static org.hamcrest.CoreMatchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
public class MathFunControllerTest {
	
	@Autowired
	private MockMvc mockMvc;
	
	@Test
	public void getGcfTest() throws Exception {
		this.mockMvc.perform(get("/get-gcf?firstNumber=12&secondNumber=16"))
			.andDo(print())
			.andExpect(status().isOk())
			.andExpect(jsonPath("$.firstNumber", is(12)))
			.andExpect(jsonPath("$.secondNumber", is(16)))
			.andExpect(jsonPath("$.gcf", is(4)));
	}

}

Now that we have seen the code, let's explain what it does. The @SpringBootTest annotation tells Spring the following:

  • To use the default ContextLoader, in this case, it's SpringBootContextLoader.
  • To automatically search for @SpringBootConfiguration.
  • To register a TestRestTemplate and/or WebTestClient. But we won't be using these.
  • To load a WebApplicationContext and provide a mock servlet environment. Used in conjunction with @AutoConfigureMockMvc for MockMvc based testing. As the name suggests, Spring will auto-confiure MockMvc and automatically inject (@Autowired) it for us.

The @Test annotation tells JUnit that the method is to be run as a test case. What does this method do? Using MockMvc, it performs a HTTP GET request on /get-gcf resource with two query parameters. It then prints the response and checks for a 200 status. By using jsonPath dot notation, we can assert the response body values with the expected values. The dollar sign represents the root member object.

Spring Boot MockMvc Example
JUnit Passed

print() output


2023-01-18 07:52:27.115  INFO 19520 --- [           main] c.b.j.controller.MathFunControllerTest   : Started MathFunControllerTest in 2.381 seconds (JVM running for 4.218)

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /get-gcf
       Parameters = {firstNumber=[12], secondNumber=[16]}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = com.blogspot.jpllosa.controller.MathFunController
           Method = com.blogspot.jpllosa.controller.MathFunController#getGcf(int, int)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"firstNumber":12,"secondNumber":16,"gcf":4}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

There you have it. A simple example of how to use MockMvc.

Monday, December 26, 2022

Test-Driven Development with Spring Boot Starter Test

In this example we utilise Spring Boot's utilities to test our application, particularly JUnit. The spring-boot-starter-test imports Spring Boot test modules as well as JUnit, AssertJ, Hamcrest, etc. So let's get to it.

Create the Project

Create your project using Spring Initializr. We'll be using Maven, Java 8, Spring Boot 2.7.7, and without dependencies. Before generating it, explore the POM, there should be spring-boot-starter-test in the test scpope. As for the project Metadata, you can put whatever you like for the group, artifact, name, etc. But this is what I've got:

Project Metadata

  • Group: com.blogspot.jpllosa
  • Artifact: tdd-junit
  • Name: TDD with JUnit
  • Description: Test-Driven Development with JUnit
  • Package name: com.blogspot.jpllosa

When you are ready, import the project to your IDE. I use Spring Tool Suite 4.

Test-Driven Development Coding Rythm

At the heart of TDD (Test-Driven Development) is unit testing. We rely on unit testing to prevent bugs. With unit tests, regression testing can be run many times a day. A comprehensive test prevents fixed bugs from coming back or finding side effect of changes.

In TDD, the rythm of coding is test a little... code a little... test a little... code a little...

So we follow these 5 steps:

  1. Write a test
  2. Watch it fail
  3. Write the code
  4. Run the tests
  5. Refactor

Write a Test

For this example we will create a greatest common factor finder between two numbers. Let's create a skeleton class called MathFun in /src/main/java. Now, let's follow the first step and write a test in /src/test/java, create MathFunTest. This signifies that MathFun is the class being tested. We're using JUnit 5 so annotate it with @SpringBootTest.

In order for us to write a test, we need to know what is a greatest common factor. For in depth details, google it. Here's a quick explanation. The greatest common factor (GCF) of a set of numbers is the largest factor that all the numbers share. The greatest common factor of 12 and 16 is 4. Factors of 12 are 1, 2, 3, 4, 6, and 12. Factors of 16 are 1, 2, 4, 8, and 16. Factors are numbers we multiply together to get another number. E.g. 2 X 6 = 12, so 2 and 6 are factors of 12. E.g. 4 x 4 = 16. The common factors are 1, 2, 4. Making 4 the GCF. Understood?

Okay, so our input will be 12 and 16 and the expected result is 4. Create a method testWithTwoPositiveNumbers and watch it fail. Right click + Run as JUnit test or you can go command line, "mvn test".

MathFun.java


package com.blogspot.jpllosa;

public class MathFun {

	public int getGCF(int x, int y) {
		return 0;
	}
}

MathFunTest.java


package com.blogspot.jpllosa;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MathFunTest {
	
	@Test
	public void testWithTwoPositiveNumbers( ) {
		MathFun mf = new MathFun();
		
		assertEquals(4, mf.getGCF(12, 16));
	}

}

Failure Result


org.opentest4j.AssertionFailedError: expected: <4> but was: <0>

Write the Code

Now that we have seen it fail. Time to make it pass. This is a quick one. We will use Euclid's algorithm for finding the GCF. Change the implementation to return the result of (y == 0) ? x : getGCF(y, x % y). Then run the tests again. This time you should have a green for a pssing test as shwon below. 

TDD with JUnit

Great!

Refactor

We trust Euclid's algorithm so we don't need to refator the code. This example is not about finding the GCF anyway but an exercise on TDD. The next thing would be to think of another test case that we can exercise on our getGCF method. Let's add another test, testWithZero, to test with a zero input. Run the test again. You should have two passing tests.


@Test
public void testWithZero( ) {
	MathFun mf = new MathFun();
	
	assertEquals(6, mf.getGCF(0, 6));
}

Here's how it looks on the command line:


2022-12-26 07:45:24.125  INFO 15812 --- [           main] com.blogspot.jpllosa.MathFunTest         : Started MathFunTest in 0.98 seconds (JVM running for 2.322)
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.804 s - in com.blogspot.jpllosa.MathFunTest

Brilliant! Now you are getting the hang of the rythm of coding.

There you have it. A simple TDD example. The complete project can be cloned from GitHub.