0% found this document useful (0 votes)
11 views29 pages

Another J Unit

Uploaded by

eba girma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views29 pages

Another J Unit

Uploaded by

eba girma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 29

Build with Confidence: A

Guide to JUnit Tests


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?

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

SHARE THIS ARTICLE

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.

What is Mocking and When Does It Come into the


Picture?

Suppose you have a class, CalculateArea , which has a function calculateArea(Type


type, Double... args) which calculates the area of a shape of the given type (circle,

square, or rectangle.)
The code goes something like this in a normal application which does not use dependency
injection:

Explain

public class CalculateArea {

SquareService squareService;

RectangleService rectangleService;
CircleService circleService;

CalculateArea(SquareService squareService, RectangleService rectangeService,

CircleService circleService)

this .squareService = squareService;

this .rectangleService = rectangeService;

this .circleService = circleService;

public Double calculateArea(Type type, Double... r )

switch (type)

case RECTANGLE:

if (r.length >= 2 )

return rectangleService.area(r[ 0 ],r[ 1 ]);

else
throw new RuntimeException ( "Missing

required params" );

case SQUARE:

if (r.length >= 1 )

return squareService.area(r[ 0 ]);

else

throw new RuntimeException ( "Missing

required param" );

case CIRCLE:

if (r.length >= 1 )

return circleService.area(r[ 0 ]);

else

throw new RuntimeException ( "Missing

required param" );

default :

throw new RuntimeException ( "Operation not

supported" );

}
}

Explain

public class SquareService {

public Double area(double r)

return r * r;

Explain

public class RectangleService {

public Double area(Double r, Double h)

return r * h;

Explain

public class CircleService {


public Double area(Double r)

return Math.PI * r * r;

public enum Type {

RECTANGLE,SQUARE,CIRCLE;

Now, if we want to unit-test the function calculateArea() of the class CalculateArea ,


then our motive should be to check whether the switch cases and exception conditions work.
We should not test whether the shape services are returning the correct values, because as
mentioned earlier, the motive of unit-testing a function is to test the logic of the function, not the
logic of the calls the function is making.
So we will mock the values returned by individual service functions
(e.g. rectangleService.area() and test the calling function
(e.g. CalculateArea.calculateArea() ) based on those mocked values.
A simple test case for the rectangle service—testing that calculateArea() indeed
calls rectangleService.area() with the correct parameters—would look like this:
Explain

import org.junit.Assert;

import org.junit.Before;
import org.junit.Test;

import org.mockito.Mockito;

public class CalculateAreaTest {

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()
{

Mockito.when(rectangleService.area( 5.0d , 4.0d )).thenReturn( 20d );

Double calculatedArea =

this .calculateArea.calculateArea(Type.RECTANGLE, 5.0d , 4.0d );

Assert.assertEquals( new Double ( 20d ),calculatedArea);

Two main lines to note here are:

 rectangleService = Mockito.mock(RectangleService.class); —This

creates a mock, which is not an actual object, but a mocked one.


 Mockito.when(rectangleService.area(5.0d,4.0d)).thenReturn(20d

); —This says that, when mocked, and

the rectangleService object’s area method is called with the specified


parameters, then return 20d .
Now, what happens when the above code is part of a Spring application?

Explain

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

@Component
public class CalculateArea {

SquareService squareService;

RectangleService rectangleService;

CircleService circleService;

public CalculateArea(@Autowired SquareService squareService,


@Autowired RectangleService rectangeService, @Autowired CircleService
circleService)

this .squareService = squareService;

this .rectangleService = rectangeService;

this .circleService = circleService;

public Double calculateArea(Type type, Double... r )

// (same implementation as before)

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

 @Autowired : Searches for the beans rectangleService , squareService ,

and circleService and injects them into the bean calculatedArea


Similarly, we create beans for other classes as well:

Explain

import org.springframework.stereotype.Service;

@Service

public class SquareService {

public Double area(double r)

return r*r;

Explain

import org.springframework.stereotype.Service;

@Service

public class CircleService {

public Double area(Double r)

{
return Math.PI * r * r;

Explain

import org.springframework.stereotype.Service;

@Service

public class RectangleService {

public Double area(Double r, Double h)

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

public class CalculateArea {

@Autowired

SquareService squareService;

@Autowired

RectangleService rectangleService;

@Autowired

CircleService circleService;

public Double calculateArea(Type type, Double... r )

// (same implementation as before)

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

public class CalculateAreaTest {

@Mock

RectangleService rectangleService;

@Mock

SquareService squareService;

@Mock

CircleService circleService;

@InjectMocks

CalculateArea calculateArea;

@Before
public void init()

MockitoAnnotations.initMocks( this );

@Test
public void calculateRectangleAreaTest()

Mockito.when(rectangleService.area( 5.0d , 4.0d )).thenReturn( 20d );

Double calculatedArea =

this .calculateArea.calculateArea(Type.RECTANGLE, 5.0d , 4.0d );


Assert.assertEquals( new Double ( 20d ),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.

What Are Test Runners, and What Types of


Runners Are There?

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

public class RectangleService {

public Double area(Double r, Double h)

log();

return r*h;

public void log() {

System.out.println( "skip this" );

}
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)

public class CalculateAreaTest {

@Spy

RectangleService rectangleService;

@Mock

SquareService squareService;

@Mock

CircleService circleService;

@InjectMocks

CalculateArea calculateArea;

@Test
public void calculateRectangleAreaTest()

Mockito.doCallRealMethod().when(rectangleService).log();

Mockito.when(rectangleService.area( 5.0d , 4.0d )).thenReturn( 20d );

Double calculatedArea =

this .calculateArea.calculateArea(Type.RECTANGLE, 5.0d , 4.0d );


Assert.assertEquals( new Double ( 20d ),calculatedArea);

How Do We Go About Testing


a Controller or RequestHandler ?
From what we learned above, the test code of a controller for our example would be something
like the below:
Explain

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

public class AreaController {

@Autowired

CalculateArea calculateArea;
@RequestMapping(value = "api/area", method = RequestMethod.GET)
@ResponseBody

public ResponseEntity calculateArea(

@RequestParam("type") String type,


@RequestParam("param1") String param1,
@RequestParam(value = "param2", required = false) String
param2

) {

try {

Double area = calculateArea.calculateArea(

Type.valueOf(type),

Double.parseDouble(param1),

Double.parseDouble(param2)

);

return new ResponseEntity (area, HttpStatus.OK);

catch (Exception e)

return new ResponseEntity (e.getCause(),

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)

public class AreaControllerTest {

@Mock

CalculateArea calculateArea;

@InjectMocks

AreaController areaController;
@Test
public void calculateAreaTest()

Mockito

.when(calculateArea.calculateArea(Type.RECTANGLE, 5.0d , 4.0d ))

.thenReturn( 20d );

ResponseEntity responseEntity =

areaController.calculateArea( "RECTANGLE" , "5" , "4" );

Assert.assertEquals(HttpStatus.OK,responseEntity.getStatusCode());

Assert.assertEquals( 20d ,responseEntity.getBody());

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.

This code is better:

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)

public class AreaControllerTest {

@Mock

CalculateArea calculateArea;

@InjectMocks
AreaController areaController;

MockMvc mockMvc;

@Before
public void init()

mockMvc = standaloneSetup(areaController).build();

@Test

public void calculateAreaTest() throws Exception {

Mockito

.when(calculateArea.calculateArea(Type.RECTANGLE, 5.0d , 4.0d ))

.thenReturn( 20d );

mockMvc.perform(

MockMvcRequestBuilders.get( "/api/area?

type=RECTANGLE&param1=5&param2=4" )

.andExpect(status().isOk())

.andExpect(content().string( "20.0" ));


}

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.

Java Integration Testing Using JUnit and Mocks

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

public class TestConfig {

@Bean

public AreaController areaController()

return new AreaController ();


}

@Bean

public CalculateArea calculateArea()

return new CalculateArea ();

@Bean

public RectangleService rectangleService()

return new RectangleService ();

@Bean

public SquareService squareService()

return new SquareService ();

@Bean

public CircleService circleService()

{
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})

public class AreaControllerIntegrationTest {

@Autowired

AreaController areaController;

MockMvc mockMvc;

@Before
public void init()

{
mockMvc = standaloneSetup(areaController).build();

@Test

public void calculateAreaTest() throws Exception {

mockMvc.perform(

MockMvcRequestBuilders.get( "/api/area?

type=RECTANGLE&param1=5&param2=4" )

.andExpect(status().isOk())

.andExpect(content().string( "20.0" ));

A few things change here:

 @ContextConfiguration(classes = {TestConfig.class}) —this tells

the test case where all the bean definitions reside.


 Now instead of @InjectMocks we use:
@Autowired

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.

Bonus: How to Create Large Object Test Data

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:

Class1 object = new Class1 ();

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

ObjectMapper objectMapper = new ObjectMapper ();

Class1 object = objectMapper.readValue(

new String (Files.readAllBytes(

Paths.get( "src/test/resources/" +fileName))

),

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.

JUnit Basics: Multiple Approaches and


Transferable Skills
It’s clear that there are many ways to write Java unit tests depending upon how we choose to
inject beans. Unfortunately, most articles on the topic tend to assume there’s only one way, so
it’s easy to get confused, especially when working with code that was written under a different
assumption. Hopefully, our approach here saves developers time in figuring out the correct way
to mock and which test runner to use.

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!

Common questions

Powered by AI

The document covers constructor injection and field injection as bean injection methods. Constructor injection is implemented by passing dependencies through a class constructor, and this does not require changes in JUnit test cases. Field injection uses @Autowired annotations on fields of the class. In JUnit tests, mocks are created with @Mock, and the actual object is prepared with @InjectMocks when using constructor injection, whereas field injection requires an additional step of activating mocks with initMocks().

The document compares constructor injection and field injection for bean dependencies. Constructor injection involves passing dependencies through a class constructor, while field injection uses annotations to automatically inject dependencies into class fields. In unit testing with JUnit, these methods affect how tests are set up and executed, particularly concerning the necessity of mocks and their initialization. Regardless of the injection method used, testing strategies need to accommodate it, such as using @InjectMocks for constructor injection and ensuring field-injected beans are appropriately mocked .

In Spring, dependency injection simplifies testing by allowing developers to decouple classes from their dependencies, thereby making them easier to test in isolation. The document demonstrates this with the CalculateArea class, where service dependencies are injected using @Autowired. This allows unit tests to replace actual service instances with mocked versions using frameworks like Mockito, ensuring that the tests focus on the logic within the class being tested, rather than external dependencies .

MockitoJUnitRunner offers advantages like automating mock initialization and injection, which simplifies the setup of unit tests. Unlike using a basic runner like BlockJUnit4ClassRunner, MockitoJUnitRunner eliminates the need to explicitly call MockitoAnnotations.initMocks(this). It reads annotations to set up mocks, reducing boilerplate code and potential errors in tests. This facilitates cleaner and more maintainable test code, especially in complex test environments with numerous dependencies .

Automated testing frameworks are crucial in continuous delivery environments because they provide confidence that changes do not break existing functionality. The document emphasizes the need for frameworks like JUnit to perform regression testing quickly and effectively, allowing code to be deployed to production as soon as it is written. This minimizes downtime and reduces the risk of introducing defects into live systems, maintaining high software quality and reliability under rapid development cycles .

The document illustrates the use of @Spy in the CalculateAreaTest class by allowing some methods to be mocked while others are executed as is. For instance, in a scenario where the RectangleService's area method includes a log method, @Spy enables the test to mock the area method while still executing the actual log method for its side effects, such as printing to the console. This allows specific parts of the application logic to be verified while still testing some real behavior .

Mocking in the CalculateArea class helps isolate and test the logic of the calculateArea() function without influencing factors from external dependencies such as SquareService, RectangleService, and CircleService. The document describes creating mock objects for these services using Mockito, which allows the test to specify what should be returned when certain methods are called. This ensures that the unit test for CalculateArea focuses solely on the logic within the function rather than the external services it interacts with .

The document suggests using a JSON schema to handle the complexity of large test data. Rather than manually creating objects with numerous fields, a JSON file containing the required data can be created and maintained. In the test class, the ObjectMapper from the Jackson library can read this JSON file and map it into the appropriate class. This approach reduces boilerplate code and makes it easier to manage and adapt test data across different tests .

Custom test runners, as introduced in the document, extend the capabilities of default JUnit runners by offering specialized functionality like automatic injection setup and management of test contexts. For instance, MockitoJUnitRunner simplifies mock setup by eliminating manual initialization and injection, which facilitates cleaner test cases. Custom runners enhance test efficiency and consistency, ensuring that test environments are configured correctly without additional overhead from the developer .

The document outlines that unit testing involves testing the smallest unit of a program (a function or procedure) to ensure it behaves as expected. It focuses on the logic of individual methods without considering interactions with other units. In contrast, integration testing involves testing multiple units together to verify that they interact correctly. While unit tests often require mocking to isolate the unit being tested, integration tests do not, as they aim to check the interaction among several units .

You might also like