The biggest problem of so called traditional unit tests is duplicate code. Even if we would create our test data and write our assertions by following the instructions given in the earlier lessons of this course, we wouldn’t be able to eliminate this problem.

The reason for this is that:

We have to create test data that is passed as an input to the system under test or returned by the stubbed methods.

We have to configure the behavior of the test doubles that are used by the system under test.

One solution to this problem is to create our test data and configure our test doubles in the setup method. Even though this solution could work, it creates three new problems:

Our setup method would be so huge that it would be very hard to read and maintain.

Our test methods would be very hard to read, write, and maintain because we cannot “see” the test data and we cannot know how the test doubles are configured.

if we have to replace the dependencies of the system under test with test doubles, we might have to create one test class per test case because different test cases might require configuration that is not compatible with the other test cases.

In other words, we have to create our test data and configure our test doubles in our test methods. The problem is that if we make changes to the system under test, we might break so many unit tests that it can literally take hours or days to fix them all. This makes no sense.

It’s clear that if we write traditional unit tests, we cannot remove all duplicate code from our test code. Luckily for us, we can solve this problem by writing nested unit tests.

Writing Nested Unit Tests

Getting the Required Dependencies

Before we can write nested unit tests, we have to get the required dependencies. We can do this by declaring the junit-hierarchicalcontextrunner dependency in our build script.

If we are using Maven, we can declare this dependency by adding the following snippet into our pom.xml file.

Writing Nested Unit Tests

Let’s create a simple test class and add a few inner classes into that class. The idea of this exercise is to demonstrate the invocation order of setup, teardown, and test methods. We can create our test class by following these steps:

First, we have to create a test class and add all setup and teardown methods into the created class. After we have added the setup and teardown methods into the created class, we have to add one test method into the test class.

Second, we have to add a new inner class called ContextA into the NestedTest class. After we have created the ContextA class, we have to add one setup, teardown, and test method into the created inner class.

After we have added this inner class into the NestedTest class, the source code of our test class looks as follows:

Third, we have to add a new inner class called ContextC into the ContextA class. After we have created the ContextC class, we have to add one setup, teardown, and test method into the created inner class.

After we have added this inner class into the ContextA class, the source code of our test class looks as follows:

This output reveals the invocation order of the setup, teardown, and test methods. Let’s go through our test methods one by one.

First, when JUnit runs the rootClassTest() method, it will invoke these methods in the following order:

beforeAllTestMethods()

beforeEachTestMethod()

rootClassTest()

afterEachTestMethod()

afterAllTestMethods()

Second, when JUnit runs the contextATest() method, it will invoke these methods in the following order:

beforeAllTestMethods()

beforeEachTestMethod()

beforeEachTestMethodOfContextA()

contextATest()

afterEachTestMethodOfContextA()

afterEachTestMethod()

afterAllTestMethods()

Third, when JUnit runs the contextCTest() method, it will invoke these methods in the following order:

beforeAllTestMethods()

beforeEachTestMethod()

beforeEachTestMethodOfContextA()

beforeEachTestMethodOfContextC()

contextCTest()

afterEachTestMethodOfContextC()

afterEachTestMethodOfContextA()

afterEachTestMethod()

afterAllTestMethods()

In other words, JUnit invokes the setup and teardown methods by following the context hierarchy of the invoked test method. This means that we can eliminate code code by putting our code to the correct place. We will talk more about this in the next lesson of this topic.

This runner supports JUnit rules as well. I left them out from this example because I didn’t want to confuse you. That being said, you can add rules into nested inner classes and JUnit will invoke them in the correct order.

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us three things:

Duplicate code is the biggest problem of so called traditional unit tests.

If we want to write nested unit tests, we have to run our unit tests by using the HierarchicalContextRunner class.

JUnit invokes the setup and teardown methods by following the context hierarchy of the invoked test method.