[go: up one dir, main page]

0% found this document useful (0 votes)
4 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)
4 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!

You might also like