Another J Unit
Another J Unit
authors are vetted experts in their fields and write on topics in which they have demonstrated
experience. All of our content is peer reviewed and validated by Toptal experts in the same field
In an age of continuous delivery, Java developers have to be confident that their changes don’t
break existing code, hence automated testing. There’s more than one valid approach to it, but
how can you keep them straight?
With the advancement of technology and industry moving from the waterfall model to Agile and
now to DevOps, changes and enhancements in an application are deployed to production the
minute they’re made. With code getting deployed to production this fast, we need to be confident
that our changes work, and also that they don’t break any preexisting functionality.
To build this confidence, we must have a framework for automatic regression testing. For doing
regression testing, there are many tests that should be carried out from an API-level point of
view, but here we’ll cover two major types of tests:
Unit testing, where any given test covers the smallest unit of a program (a function
or procedure). It may or may not take some input parameters and may or may not
return some values.
Integration testing, where individual units are tested together to check whether all
the units interact with each other as expected.
There are numerous frameworks available for every programming language. We will focus on
writing unit and integration testing for a web app written in Java’s Spring framework.
Most of the time, we write methods in a class, and these, in turn, interact with methods of some
other class. In today’s world—especially in enterprise applications—the complexity of
applications is such that a single method might call more than one method of multiple classes. So
when writing the unit test for such a method, we need a way to return mocked data from those
calls. This is because the intent of this unit test is to test only one method and not all the calls that
this particular method makes.
Let’s jump into Java unit testing in Spring using the JUnit framework. We’ll start with something
you may have heard of: mocking.
square, or rectangle.)
The code goes something like this in a normal application which does not use dependency
injection:
Explain
SquareService squareService;
RectangleService rectangleService;
CircleService circleService;
CircleService circleService)
switch (type)
case RECTANGLE:
if (r.length >= 2 )
else
throw new RuntimeException ( "Missing
required params" );
case SQUARE:
if (r.length >= 1 )
else
required param" );
case CIRCLE:
if (r.length >= 1 )
else
required param" );
default :
supported" );
}
}
Explain
return r * r;
Explain
return r * h;
Explain
return Math.PI * r * r;
RECTANGLE,SQUARE,CIRCLE;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
RectangleService rectangleService;
SquareService squareService;
CircleService circleService;
CalculateArea calculateArea;
@Before
public void init()
rectangleService = Mockito.mock(RectangleService.class);
squareService = Mockito.mock(SquareService.class);
circleService = Mockito.mock(CircleService.class);
calculateArea = new
CalculateArea (squareService,rectangleService,circleService);
@Test
public void calculateRectangleAreaTest()
{
Double calculatedArea =
Explain
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CalculateArea {
SquareService squareService;
RectangleService rectangleService;
CircleService circleService;
Here we have two annotations for the underlying Spring framework to detect at the time of
context initialization:
@Component : Creates a bean of type CalculateArea
Explain
import org.springframework.stereotype.Service;
@Service
return r*r;
Explain
import org.springframework.stereotype.Service;
@Service
{
return Math.PI * r * r;
Explain
import org.springframework.stereotype.Service;
@Service
return r*h;
Now if we run the tests, the results are the same. We used constructor injection here, and
fortunately, don’t to change our JUnit test case.
But there is another way to inject the beans of square, circle, and rectangle services: field
injection. If we use that, then our JUnit test case will need some minor changes.
We won’t go into the discussion of which injection mechanism is better, as that’s not in the
scope of the article. But we can say this: No matter what type of mechanism you use to inject
beans, there’s always a way to write JUnit tests for it.
In the case of field injection, the code goes something like this:
Explain
@Component
@Autowired
SquareService squareService;
@Autowired
RectangleService rectangleService;
@Autowired
CircleService circleService;
Note: Since we are using field injection, there is no need for a parameterized constructor, so the
object is created using the default one and values are set using the field injection mechanism.
The code for our service classes remains the same as above, but the code for the test class is as
follows:
Explain
@Mock
RectangleService rectangleService;
@Mock
SquareService squareService;
@Mock
CircleService circleService;
@InjectMocks
CalculateArea calculateArea;
@Before
public void init()
MockitoAnnotations.initMocks( this );
@Test
public void calculateRectangleAreaTest()
Double calculatedArea =
A few things go differently here: not the basics, but the way we achieve it.
First, the way we mock our objects: We use @Mock annotations along with initMocks() to
create mocks. Second, we inject mocks into the actual object using @InjectMocks along
with initMocks() .
This is just done to reduce the number of lines of code.
In the above sample, the basic runner that is used to run all the tests
is BlockJUnit4ClassRunner which detects all the annotations and run all the tests
accordingly.
If we want some more functionality then we may write a custom runner. For example, in the
above test class, if we want to skip the line MockitoAnnotations.initMocks(this); then
we could use a different runner that is built on top of BlockJUnit4ClassRunner ,
e.g. MockitoJUnitRunner .
Using MockitoJUnitRunner , we don’t even need to initialize mocks and inject them. That
will be done by MockitoJUnitRunner itself just by reading annotations.
(There’s also SpringJUnit4ClassRunner , which initializes
the ApplicationContext needed for Spring integration testing—just like
an ApplicationContext is created when a Spring application starts. This we’ll cover later.)
Partial Mocking
When we want an object in the test class to mock some method(s), but also call some actual
method(s), then we need partial mocking. This is achieved via @Spy in JUnit.
Unlike using @Mock , with @Spy , a real object is created, but the methods of that object can be
mocked or can be actually called—whatever we need.
For example, if the area method in the class RectangleService calls an extra
method log() and we actually want to print that log, then the code changes to something like
the below:
Explain
@Service
log();
return r*h;
}
If we change the @Mock annotation of rectangleService to @Spy , and also make some code
changes as shown below then in the results we would actually see the logs getting printed, but
the method area() will be mocked. That is, the original function is run solely for its side-
effects; its return values are replaced by mocked ones.
Explain
@RunWith(MockitoJUnitRunner.class)
@Spy
RectangleService rectangleService;
@Mock
SquareService squareService;
@Mock
CircleService circleService;
@InjectMocks
CalculateArea calculateArea;
@Test
public void calculateRectangleAreaTest()
Mockito.doCallRealMethod().when(rectangleService).log();
Double calculatedArea =
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Autowired
CalculateArea calculateArea;
@RequestMapping(value = "api/area", method = RequestMethod.GET)
@ResponseBody
) {
try {
Type.valueOf(type),
Double.parseDouble(param1),
Double.parseDouble(param2)
);
catch (Exception e)
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Explain
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@RunWith(MockitoJUnitRunner.class)
@Mock
CalculateArea calculateArea;
@InjectMocks
AreaController areaController;
@Test
public void calculateAreaTest()
Mockito
.thenReturn( 20d );
ResponseEntity responseEntity =
Assert.assertEquals(HttpStatus.OK,responseEntity.getStatusCode());
Looking at the above controller test code, it works fine, but it has one basic issue: It only tests
the method call, not the actual API call. All those test cases where the API parameters and status
of API calls need to tested for different inputs are missing.
Explain
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static
org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
@RunWith(SpringJUnit4ClassRunner.class)
@Mock
CalculateArea calculateArea;
@InjectMocks
AreaController areaController;
MockMvc mockMvc;
@Before
public void init()
mockMvc = standaloneSetup(areaController).build();
@Test
Mockito
.thenReturn( 20d );
mockMvc.perform(
MockMvcRequestBuilders.get( "/api/area?
type=RECTANGLE¶m1=5¶m2=4" )
.andExpect(status().isOk())
Here we can see how MockMvc takes the job of performing actual API calls. It also has some
special matchers like status() and content() which make it easy to validate the content.
Now that we know individual units of the code work, let’s conduct some Java integration testing
to make sure these units interact with each other as expected.
First, we need to instantiate all the beans, the same stuff that happens at the time of Spring
context initialization during application startup.
For this, we define all the beans in a class, let’s say TestConfig.java :
Explain
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Bean
@Bean
@Bean
@Bean
@Bean
{
return new CircleService ();
Now let’s see how we use this class and write a JUnit integration test:
Explain
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static
org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfig.class})
@Autowired
AreaController areaController;
MockMvc mockMvc;
@Before
public void init()
{
mockMvc = standaloneSetup(areaController).build();
@Test
mockMvc.perform(
MockMvcRequestBuilders.get( "/api/area?
type=RECTANGLE¶m1=5¶m2=4" )
.andExpect(status().isOk())
AreaController areaController;
Everything else remains the same. If we debug the test, we would see that the code actually runs
until the last line of the area() method in RectangleService where return r*h is
calculated. In other words, the actual business logic runs.
This does not mean that there is no mocking of method calls or database calls available in
integration testing. In the above example, there was no third-party service or database used,
hence we did not need to use mocks. In real life, such applications are rare, and we’ll often hit a
database or third-party API, or both. In that case, when we create the bean in
the TestConfig class, we don’t create the actual object, but a mocked one, and use it wherever
needed.
Often what stops back-end developers in writing unit or integration tests is the test data that we
have to prepare for every test.
Normally if the data is small enough, having one or two variables, then it’s easy to just create an
object of a test data class and assign some values.
For example, if we are expecting a mocked object to return another object, when a function is
called on the mocked object we would do something like this:
object.setVariable1( 1 );
object.setVariable2( 2 );
And then in order to use this object, we would do something like this:
Mockito.when(service.method(arguments...)).thenReturn(object);
This is fine in the above JUnit examples, but when the member variables in the
above Class1 class keep on increasing, then setting individual fields becomes quite a pain.
Sometimes it might even happen that a class has another non-primitive class member defined.
Then, creating an object of that class and setting individual required fields further increases the
development effort just to accomplish some boilerplate.
The solution is to generate a JSON schema of the above class and add the corresponding data in
the JSON file once. Now in the test class where we create the Class1 object, we don’t need to
create the object manually. Instead, we read the JSON file and, using ObjectMapper , map it
into the required Class1 class:
Explain
),
Class1.class
);
This is a one-time effort of creating a JSON file and adding values to it. Any new tests after that
can use a copy of that JSON file with fields changed according to the needs of the new test.
Irrespective of the language or framework we use—perhaps even any new version of Spring or
JUnit—the conceptual base remains the same as explained in the above JUnit tutorial. Happy
testing!