Test With Spring Course
Save Time by Writing Less Test Code
  • Home
  • Pre-Sales FAQ
  • Support
  • Log In

/ August 7, 2016 / petrikainulainen

Writing Nested Unit Tests

Lesson Progress:
← Back to Topic

After we have finished this lesson, we:

  • Understand why we should write nested unit tests.
  • Know how we can write nested unit tests.

This lesson assumes that:

  • You are familiar with JUnit 4
  • You can use custom test runners
  • You know how you can run unit tests by using Maven or Gradle

Watch the Lesson

 

The text version of this lesson is given in the following:

The Problems of Traditional Unit Tests

Before we can talk about the problems of traditional unit tests, we have to define the term traditional unit test. A traditional test class fulfills these conditions:

  • It can contain setup methods, teardown methods, and test methods.
  • All methods must be added directly to the test class.
  • It can use a custom test runner.
  • It can use JUnit 4 rules.

A traditional test class might look as follows:

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(UnitTest.class)
public class TraditionalTest {

    @BeforeClass
    public static void beforeAllTestMethods() {
    }

    @Before
    public void beforeEachTestMethod() {
    }

    @After
    public void afterEachTestMethod() {
    }

    @AfterClass
    public static void afterAllTestMethods() {
    }

    @Test
    public void testOne() {
    }
	
    @Test
    public void testTwo() {
    }
}

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.

<dependency>
	<groupId>de.bechte.junit</groupId>
	<artifactId>junit-hierarchicalcontextrunner</artifactId>
	<version>4.12.1</version>
	<scope>test</scope>
</dependency>

If we are using Gradle, we can declare this dependency by adding the following snippet into our build.gradle file.

testCompile(
	'de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1'
)

Next, we have to configure the test runner that runs our unit tests.

Configuring the Test Runner

If we want to write nested unit tests, we have to ensure that our unit tests are run by the HierarchicalContextRunner class. We can do this by annotating our test class with the @RunWith annotation.

After we have configured the used test runner, the source code of our class looks as follows:

import de.bechte.junit.runners.context.HierarchicalContextRunner;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

@RunWith(HierarchicalContextRunner.class)
@Category(UnitTest.class)
public class NestedTest {

}

Let’s find out how we can write nested unit tests.

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.

The source code of our test class looks as follows:

import de.bechte.junit.runners.context.HierarchicalContextRunner;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

@RunWith(HierarchicalContextRunner.class)
@Category(UnitTest.class)
public class NestedTest {

    @BeforeClass
    public static void beforeAllTestMethods() {
        System.out.println("Invoked once before all test methods");
    }

    @Before
    public void beforeEachTestMethod() {
        System.out.println("Invoked before each test method");
    }

    @After
    public void afterEachTestMethod() {
        System.out.println("Invoked after each test method");
    }

    @AfterClass
    public static void afterAllTestMethods() {
        System.out.println("Invoked once after all test methods");
    }

    @Test
    public void rootClassTest() {
        System.out.println("Root class test");
    }
}

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:

import de.bechte.junit.runners.context.HierarchicalContextRunner;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

@RunWith(HierarchicalContextRunner.class)
@Category(UnitTest.class)
public class NestedTest {

    @BeforeClass
    public static void beforeAllTestMethods() {
        System.out.println("Invoked once before all test methods");
    }

    @Before
    public void beforeEachTestMethod() {
        System.out.println("Invoked before each test method");
    }

    @After
    public void afterEachTestMethod() {
        System.out.println("Invoked after each test method");
    }

    @AfterClass
    public static void afterAllTestMethods() {
        System.out.println("Invoked once after all test methods");
    }

    @Test
    public void rootClassTest() {
        System.out.println("Root class test");
    }

    public class ContextA {

        @Before
        public void beforeEachTestMethodOfContextA() {
            System.out.println("Invoked before each test method of context A");
        }

        @After
        public void afterEachTestMethodOfContextA() {
            System.out.println("Invoked after each test method of context A");
        }

        @Test
        public void contextATest() {
            System.out.println("Context A test");
        }
	}
}

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:

import de.bechte.junit.runners.context.HierarchicalContextRunner;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

@RunWith(HierarchicalContextRunner.class)
@Category(UnitTest.class)
public class NestedTest {

    @BeforeClass
    public static void beforeAllTestMethods() {
        System.out.println("Invoked once before all test methods");
    }

    @Before
    public void beforeEachTestMethod() {
        System.out.println("Invoked before each test method");
    }

    @After
    public void afterEachTestMethod() {
        System.out.println("Invoked after each test method");
    }

    @AfterClass
    public static void afterAllTestMethods() {
        System.out.println("Invoked once after all test methods");
    }

    @Test
    public void rootClassTest() {
        System.out.println("Root class test");
    }

    public class ContextA {

        @Before
        public void beforeEachTestMethodOfContextA() {
            System.out.println("Invoked before each test method of context A");
        }

        @After
        public void afterEachTestMethodOfContextA() {
            System.out.println("Invoked after each test method of context A");
        }

        @Test
        public void contextATest() {
            System.out.println("Context A test");
        }
		
        public class ContextC {

            @Before
            public void beforeEachTestMethodOfContextC() {
                System.out.println("Invoked before each test method of context C");
            }
			
            @After
            public void afterEachTestMethodOfContextC() {
                System.out.println("Invoked after each test method of context C");
            }

            @Test
            public void contextCTest() {
                System.out.println("Context C test");
            }
        }
	}
}

We have now created a test class that contains nested unit tests. When we run our unit tests, we see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Invoked once before all test methods
Running com.testwithspring.starter.unittests.NestedTest
Invoked before each test method
Test on root level
Invoked after each test method
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec - 
in com.testwithspring.starter.unittests.NestedTest

Running com.testwithspring.starter.unittests.NestedTest$ContextA
Invoked before each test method
Invoked before each test method of context A
Context A test
Invoked after each test method of context A
Invoked after each test method
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec - 
in com.testwithspring.starter.unittests.NestedTest$ContextA

Running com.testwithspring.starter.unittests.NestedTest$ContextA$ContextC
Invoked before each test method
Invoked before each test method of context A
Invoked before each test method of context C
Context C test
Invoked after each test method of context C
Invoked after each test method of context A
Invoked after each test method
Invoked once after all test methods
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec - 
in com.testwithspring.starter.unittests.NestedTest$ContextA$ContextC

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:

  1. beforeAllTestMethods()
  2. beforeEachTestMethod()
  3. rootClassTest()
  4. afterEachTestMethod()
  5. afterAllTestMethods()

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

  1. beforeAllTestMethods()
  2. beforeEachTestMethod()
  3. beforeEachTestMethodOfContextA()
  4. contextATest()
  5. afterEachTestMethodOfContextA()
  6. afterEachTestMethod()
  7. afterAllTestMethods()

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

  1. beforeAllTestMethods()
  2. beforeEachTestMethod()
  3. beforeEachTestMethodOfContextA()
  4. beforeEachTestMethodOfContextC()
  5. contextCTest()
  6. afterEachTestMethodOfContextC()
  7. afterEachTestMethodOfContextA()
  8. afterEachTestMethod()
  9. 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.

Get the source code from Github

← Previous Lesson Next Lesson →

Can I help you?

This is a free sample lesson of my Test With Spring course. If this lesson helped you to solve your problem, you should find out how my testing course can help you.

Support and Privacy

  • Pre-Sales FAQ
  • Support
  • Cookie Policy
  • No Bullshit Privacy Policy
  • No Bullshit Terms and Conditions

Test With Spring Course

  • Starter Package
  • Intermediate Package
  • Master Package

Free Sample Lessons

  • Introduction to JUnit 4
  • Introduction to Unit Testing
  • Introduction to Integration Testing
  • Introduction to End-to-End Testing
  • Introduction to Spock Framework
  • Introduction to Integration Testing – Spock Edition
  • Writing End-to-End Tests With Spock Framework

Copyright Koodikupla Oy 2016 — Built on Thesis by Themedy