Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Automate a Java Unit Test, Including Mocking and Assertions

DZone's Guide to

How to Automate a Java Unit Test, Including Mocking and Assertions

Want to learn how to automate a Java Unit Test? Check out this post on how to use mocking, assertions, and the Parasoft Jtest Unit Test Assistant to automate unit tests.

· Java Zone ·
Free Resource

Verify, standardize, and correct the Big 4 + more– name, email, phone and global addresses – try our Data Quality APIs now at Melissa Developer Portal!

Good unit tests are a great way to make sure that your code works today and continues to work in the future. A comprehensive suite of tests, with good code-based and behavior-based coverage, can save an organization a lot of time and headaches. And, yet, it is not uncommon to see projects where not enough tests were written. In fact, some developers have even been arguing against their use completely.

Where Is the test?

There are many reasons why developers don’t write enough unit tests. One of the biggest reasons is the amount of time that they take to build and maintain, especially in large, complex projects. In complex projects, often a unit test needs to instantiate and configure a lot of objects. This takes a lot of time to set up and can make the test as complex (or more complex) than the code it is testing.

Let’s look at an example in Java:

public LoanResponse requestLoan(LoanRequest loanRequest, LoanStrategy strategy)
{
    LoanResponse response = new LoanResponse();
    response.setApproved(true);

    if (loanRequest.getDownPayment().compareTo(loanRequest.getAvailableFunds()) > 0) {
        response.setApproved(false);
        response.setMessage("error.insufficient.funds.for.down.payment");
        return response;
    }

    if (strategy.getQualifier(loanRequest) < strategy.getThreshold(adminManager)) {
        response.setApproved(false);
        response.setMessage(getErrorMessage());
    }
    return response;
}


Here, we have a method that processes a  LoanRequest, generating a  LoanResponse. Note here the  LoanStrategy argument, which is used to process the  LoanRequest. The strategy object may be complex, because it may access a database, external system, or throw a  RuntimeException. To write a test for the  requestLoan(), I need to worry about which type of  LoanStrategy I am testing with. And, I probably need to test my method with a variety of  LoanStrategy implementations andLoanRequest configurations.

A unit test for the  requestLoan() may look like this:

@Test
public void testRequestLoan() throws Throwable
{
    // Set up objects
    DownPaymentLoanProcessor processor = new DownPaymentLoanProcessor();
    LoanRequest loanRequest = LoanRequestFactory.create(1000, 100, 10000);
    LoanStrategy strategy = new AvailableFundsLoanStrategy();
    AdminManager adminManager = new AdminManagerImpl();
    underTest.setAdminManager(adminManager);
    Map<String, String> parameters = new HashMap<>();
    parameters.put("loanProcessorThreshold", "20");
    AdminDao adminDao = new InMemoryAdminDao(parameters);
    adminManager.setAdminDao(adminDao);

    // Call the method under test
    LoanResponse response = processor.requestLoan(loanRequest, strategy);

    // Assertions and other validations
}


As you can see, there’s a whole section of my test that just creates objects and configures parameters. It wasn’t obvious looking at the  requestLoan() method what objects and parameters need to be set up. To create this example, I had to run the test, add some configuration, and then re-run it again and repeat the process over and over. I spent way too much time figuring out how to configure the  AdminManager and the  LoanStrategy, instead of focusing on my method and what needed to be tested there. And, I still need to expand my test to cover more  LoanRequestcases, strategies, and parameters for the AdminDao.

Additionally, by using real objects to test with, my test is actually validating more than just the behavior of  requestLoan()  — I am depending on the behavior of the  AvailableFundsLoanStrategy ,  AdminManagerImpl , and  AdminDao  in order for my test to run effectively. I am testing those classes, too. In some cases, this is desirable, but, in other cases, it is not. Plus, if one of those other classes change, the test may start failing even though the behavior of the  requestLoan()didn’t change. For this test, we would rather isolate the class under test from its dependencies.

Using Mock Objects

One solution for the complexity problem is to mock those complex objects. For this example, I will start by using a mock for the  LoanStrategyparameter:

@Test
public void testRequestLoan() throws Throwable
{
    // Set up objects
    DownPaymentLoanProcessor processor = new DownPaymentLoanProcessor();
    LoanRequest loanRequest = LoanRequestFactory.create(1000, 100, 10000);
    LoanStrategy strategy = Mockito.mock(LoanStrategy.class);
    Mockito.when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(20.0d);
    Mockito.when(strategy.getThreshold(any(AdminManager.class))).thenReturn(20.0d);

    // Call the method under test
    LoanResponse response = processor.requestLoan(loanRequest, strategy);

    // Assertions and other validations
}


Let’s look at what’s happening here. We create a mocked instance of the  LoanStrategy  using  Mockito.mock(). Since we know that the  getQualifier() and the  getThreshold() will be called on the strategy, we define the return values for those calls using  Mockito.when(…).thenReturn(). For this test, we don’t care what the  LoanRequest instance’s values are, nor do we need a real  AdminManager anymore, because the  AdminManager was only used by the real  LoanStrategy.

Additionally, since we aren't using a real  LoanStrategy, we don’t care what the concrete implementations of the  LoanStrategy might do. We don’t need to set up test environments, dependencies, or complex objects. We are focused on testing the  requestLoan() – not  LoanStrategyor AdminManager. The code-flow of the method under test is directly controlled by the mock.

This test is a lot easier to write with Mockito than it would have been if I had to create a complex  LoanStrategy instance. But, there are still some challenges:

  • For complex applications, tests may require lots of mocks
  • If you are new to Mockito, you need to learn its syntax and patterns
  • You may not know which methods need to be mocked
  • When the application changes, the tests (and mocks) need to be updated too

Solving Mocking Challenges With Parasoft Jtest

We created the Parasoft Jtest Unit Test Assistant to help address the challenges above. The Unit Test Assistant is a component of Parasoft Jtest, which helps automate some of the most difficult parts of creating and maintaining unit tests with mocks. For the above example, the Unit Test Assistant can auto-generate a test for  requestLoan() with a single button-click, including all of the mocking and validations you see in the example test.

Unit Test Assistant’s Toolbar with requestLoan() was selected. I used the “Regular” action in the Unit Test Assistant (UTA) to generate the following test:

@Test
public void testRequestLoan() throws Throwable
{
    // Given
    DownPaymentLoanProcessor underTest = new DownPaymentLoanProcessor();

    // When
    double availableFunds = 0.0d; // UTA: default value
    double downPayment = 0.0d; // UTA: default value
    double loanAmount = 0.0d; // UTA: default value

    LoanRequest loanRequest = LoanRequestFactory.create(availableFunds, downPayment, loanAmount);
    LoanStrategy strategy = mockLoanStrategy();
    LoanResponse result = underTest.requestLoan(loanRequest, strategy);

    // Then
    // assertNotNull(result);
}


All the mocking for this test happens in a helper method:

private static LoanStrategy mockLoanStrategy() throws Throwable
{
    LoanStrategy strategy = mock(LoanStrategy.class);
    double getQualifierResult = 0.0d; // UTA: default value
    when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(getQualifierResult);

    double getThresholdResult = 0.0d; // UTA: default value
    when(strategy.getThreshold(any(AdminManager.class))).thenReturn(getThresholdResult);
    return strategy;
}


All the necessary mocking is set up for me — the Unit Test Assistant detected the method calls to  getQualifier()andgetThreshold()and mocked the methods. Once I configure values in my test for availableFundsdownPayment, etc, the test is ready to run (I could also generate a parameterized test for better coverage!). Note also that the assistant provides some guidance as to which values to change by its comments, “UTA: default value,” making testing easier.

This saves a lot of time in generating tests, especially if I don’t know what needs to be mocked or how to use the Mockito API.

Handling Code Changes

When the application logic changes, the tests often need to change also. If the test is well-written, it should fail if you update the code without updating the test. Often, the biggest challenge in updating the test is understanding what needs to be updated and how exactly to perform that update. If there are lots of mocks and values, it can be difficult to track down what the necessary changes are.

To illustrate this, let’s make some changes to the code under test:

public LoanResponse requestLoan(LoanRequest loanRequest, LoanStrategy strategy)
{
    ...

    String result = strategy.validate(loanRequest);
    if (result != null && !result.isEmpty()) {
        response.setApproved(false);
        response.setMessage(result);
        return response;
    }

    ...

    return response;
}


We have added a new method to  LoanStrategy – validate() and are now calling it from the  requestLoan(). The test may need to be updated to specify whatvalidate()should return.

Without changing the generated test, let’s run it within the Unit Test Assistant:

Image title

The Unit Test Assistant detected thatvalidate()was called on the mocked LoanStrategyargument during my test run. Since the method has not been set up for the mock, the Unit Test Assistant recommends that I mock thevalidate()method. The “Mock it” quick-fix action updates the test automatically. This is a simple example. But, for complex code where it isn’t easy to find the missing mock, the recommendation and quick-fix can save us a lot of debugging time.

After updating the test using the quick-fix, I can see the new mock and set the desired value for  validateResult:

private static LoanStrategy mockLoanStrategy() throws Throwable
{
    LoanStrategy strategy = mock(LoanStrategy.class);
    String validateResult = ""; // UTA: default value
    when(strategy.validate(any(LoanRequest.class))).thenReturn(validateResult);
    double getQualifierResult = 20.0d;
    when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(getQualifierResult);

    double getThresholdResult = 20.0d;
    when(strategy.getThreshold(any(AdminManager.class))).thenReturn(getThresholdResult);
    return strategy;
}


I can configure the validateResult with a non-empty value to test the use-case where the method enters the new block of code, or I can use an empty value (or null) to validate behavior when the new block is not entered.

The Unit Test Assistant also provides some useful tools for analyzing the test flow. For instance, here is the flow tree for our test run:

Image title

When the test ran, I can see that the test created a new mock for the  LoanStrategy and mocked the  validate(),  getQualifier(), and  getThreshold() methods. I can select method calls and see (in the variables view) what the arguments were sent to that call, and what value was returned (or exceptions thrown). When debugging tests, this can be much easier to use and understand than digging through logfiles.

The Parasoft Jtest Unit Test Assistant helps create and maintain unit tests with less time and effort, helping you reduce the complexity associated with mocking. The Unit Test Assistant also makes many other kinds of recommendations to improve existing tests based on runtime data, and has support for parameterized tests, Spring application tests, and PowerMock (for mocking static methods and constructors). In addition, Parasoft Jtest provides other functionality, such as coverage data, static analysis, and integrations with sophisticated reporting and analytics tools, such as Parasoft DTP.

Developers! Quickly and easily gain access to the tools and information you need! Explore, test and combine our data quality APIs at Melissa Developer Portal – home to tools that save time and boost revenue. 

Topics:
java ,tutorial ,mocking ,assertions ,unit testing ,parasoft jtest unit test assistant ,jtest unit assistant

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}