DEV Community

Irvin Gil
Irvin Gil

Posted on

Leveraging Debugging Features of IntelliJ IDE for Writing Unit Test

"Unit testing is not only about testing the code you've written, it's also about testing the assumptions you've made about how your code should work." - Steve Fenton

Debugging is an essential part of software development that is often overlooked or underestimated. In this post, I will discuss how utilizing the built-in debugging features in your IDE can enhance your developer experience. Specifically, I will focus on IntelliJ IDE and demonstrate how its debugging tools can assist in improving the quality and accuracy of our unit tests.

Why GIF
You might question the use of a debugger to debug unit tests rather than the code itself. Nonetheless, when writing unit tests, we often utilize frameworks that incorporate mocks. Debugging the test functions individually with the debugger enables us to view the interactions and responses of other functions included in the coverage. We can also ensure that the functions included in the coverage are properly mocked, allowing us to obtain the desired test results. I'm not trying to argue my point, but rather to demonstrate that the debugger can be a helpful tool for writing unit tests, especially if you're struggling to create them.

We'll examine a functional unit test and intentionally introduce errors to demonstrate how the debugger can identify issues. The endpoint controller method we'll be examining belongs to the "Leave Management Application," which my colleagues and I developed during our internship a few months ago. Although the code may not comply with clean code standards, we'll use it as an example without any adverse effects.

Before i forget to mention, we used spring-boot framework for the project and spock for unit testing.

//Controller Endpoint Function
    @ResponseStatus(HttpStatus.OK)
    @PutMapping("api/v1/leave/{leaveId}")
    public LeaveResponse changeLeaveStatus(@PathVariable Long leaveId, @RequestBody LeaveStatusRequest leaveStatusRequest) {
        try {
            Leave leave = leaveService.fetchLeaveById(leaveId).orElseThrow(ResourceNotFoundException::new);
            leaveService.modifyLeaveStatus(leave, leaveStatusRequest);
            return new LeaveResponse(leave);
        } catch (InvalidOperationException e) {
            throw new InvalidOperationException(
                    "UNABLE_TO_CHANGE_STATUS", "Leave status is not allowed to be modified.");
        }
    }
Enter fullscreen mode Exit fullscreen mode
//One of the unit test functions
    def "changeLeaveStatus should modify a pending leave"() {
        given:
        Long leaveId = 2L
        Leave leave = new Leave(2L, startDate, endDate, 2, LeaveStatus.PENDING, "sick", employee2, employee1)

        LeaveStatusRequest leaveStatusRequest = Mock(LeaveStatusRequest)
        leaveStatusRequest.getStatus() >> LeaveStatus.APPROVED

        leaveService.fetchLeaveById(2L) >> Optional.of(leave)
        leaveService.modifyLeaveStatus(leave, leaveStatusRequest) >> leave.setStatus(leaveStatusRequest.getStatus())

        when:
        LeaveResponse actual = leaveController.changeLeaveStatus(leaveId, leaveStatusRequest)

        then:
        LeaveStatus.APPROVED == actual.getStatus()
    }
Enter fullscreen mode Exit fullscreen mode

In the above unit test, we are testing whether the status of the entity value returned by changeLeaveStatus() is equal to Approved. We will comment out one of the mocks declared in the when section of the unit test (leaveService.modifyLeaveStatus(leave, leaveStatusRequest) >> leave.setStatus(leaveStatusRequest.getStatus())). As a result, the unit test is expected to fail because one of the service function calls inside the focus function has not been properly mocked.

Failing Unit Test

I understand that this is just a small function, and you may assume that the logs from the run configuration will indicate the origin of the null pointer. However, this is often not the case for endpoint functions that consist of private function calls. With private function calls, you can only reference and mock out the public functions, and this is where the debugger becomes useful in scenarios like this. And so, just to provide a straightforward example, we will the simple function mentioned above to illustrate how the debugger can be used to identify where the problem originated.

Shifting to our IntelliJ IDE, we will now place breakpoints on the lines of code in the endpoint function. Afterward, we'll switch back to the unit test function (in the Spec file) and click on the green play ▶ button to run the test. However, this time, we will select the Debug option with the 🐞 icon.

Setting breakpoints on the  raw `checkLeaveStatus` endraw  function

Running a unit test with the debugger enables us to use watchers to observe the state and values inside variables. We can also observe the return values of our function calls. With this, we can determine whether certain interactions within our function or even function calls to other services are properly mocked.

Top comments (0)