Mastering Unit Testing Using Mockito and JUnit

3 (2 reviews total)
By Sujoy Acharya
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies

About this book

It is insanity to keep doing things the same way and expect them to improve. Any program is useful only when it is functional; hence, before applying complex tools, patterns, or APIs to your production code, checking software functionality is must. Automated JUnit tests help you verify your assumptions continuously, detect side effects quickly, and also help you save time.

This book will provide the skills you need to successfully build and maintain meaningful JUnit test cases. You will begin with how to use advanced JUnit 4 features, improve code coverage, automate JUnit tests, monitor code quality, write JUnit tests for the database and web tier refactor legacy code, mock external dependencies using Mockito, and write testable code using test-driven development. By sequentially working through the steps in each chapter, you will quickly master the advanced JUnit features.

Publication date:
July 2014
Publisher
Packt
Pages
314
ISBN
9781783982509

 

Chapter 1. JUnit 4 – a Total Recall

This chapter covers the unit testing concept, the JUnit 4 framework, the Eclipse setup, and the advanced features of JUnit 4. In JUnit 4, you will be introduced to the JUnit framework briefly to get you up and running. We will discuss the concept surrounding JUnit essentials, annotations, assertion, the @RunWith annotation, and exception handling so that you have an adequate background on how JUnit 4 works. Advanced readers can skip to the next section.

In JUnit 4++, we will explore the advanced topics of JUnit 4 and deep dive into parameterized tests, Hamcrest matchers and assertThat, the assumption, theory, timeout, categories, rules, test suites, and tests order.

 

Defining unit testing


A test is an assessment of our knowledge, a proof of concept, or an examination of data. A class test is an examination of our knowledge to ascertain whether we can go to the next level. For software, it is the validation of functional and nonfunctional requirements before it is shipped to a customer.

Unit testing code means validation or performing the sanity check of code. Sanity check is a basic test to quickly evaluate whether the result of a calculation can possibly be true. It is a simple check to see whether the produced material is coherent.

It's a common practice to unit test the code using print statements in the main method or by executing the application. Neither of them is the correct approach. Mixing up production code with tests is not a good practice. Testing logic in the production code is a code smell, though it doesn't break the code under the test. However, this increases the complexity of the code and can create severe maintenance problem or cause system failure if anything gets misconfigured. Print statements or logging statements are executed in the production system and print unnecessary information. They increase execution time and reduce code readability. Also, junk logging information can hide a real problem, for instance, you may overlook a critical deadlock or a hung thread warning because of excessive logging of junk.

Unit testing is a common practice in test-driven development (TDD). TDD is an evolutionary development approach. It offers test-first development where the production code is written only to satisfy a test, and the code is refactored to improve its quality. In TDD, unit tests drive the design. You write code to satisfy a failing test, so it limits the code you write to only what is needed. The tests provide a fast, automated regression for refactoring and new enhancements.

Kent Beck is the originator of Extreme Programming and TDD. He has authored many books and papers.

Generally, all tests are included in the same project but under a different directory/folder. Thus, a org.packt.Bar.java class will have a org.packt.BarTest.java test. These will be in the same package (org.packt) but will be organized in the: src/org/foo/Bar.java and test/org/foo/BarTest.java directories, respectively.

Our customers do not execute the unit tests, so we don't deliver the test source folder to them. Having the code and test in the same package allows the test to access protected and default methods/properties. This is particularly useful while working with the legacy code.

Java code can be unit tested using a code-driven unit testing framework. The following are a few of the available code-driven unit testing frameworks for Java:

  • SpryTest

  • Jtest

  • JUnit

  • TestNG

JUnit is the most popular and widely used unit testing framework for Java. We will explore JUnit 4 in the next section.

 

Working with JUnit 4


JUnit is a unit testing framework for Java. It allows developers to unit test the code elegantly. Apparently, TestNG is cleaner than JUnit, but JUnit is far more popular than TestNG. JUnit has a better mocking framework support such as Mockito, which offers a custom JUnit 4 runner.

The latest version of JUnit (4.11) can be downloaded from https://github.com/junit-team/junit/wiki/Download-and-Install.

JUnit 4 is an annotation-based, flexible framework. Its predecessor has many downsides. The following are the advantages of JUnit 4 over its predecessor:

  • Instead of inheriting from junit.framework.Testcase, any class can be a test class

  • The setUp and tearDown methods are replaced by the @before and @after annotations

  • Any public method annotated as @test can be a test method

In this chapter, we will use Eclipse to execute the JUnit tests; in the following chapters, we will be using Ant, Maven, and Gradle to execute tools. Eclipse is an integrated development environment, and can be used to develop applications in Java. It can be downloaded from http://www.eclipse.org/downloads/. As of today, the latest IDE version is KEPLER (4.3).

Note

Since 2006, Eclipse releases a project annually. It started with the name Callisto (starts with a C). Lexicographically, Eclipse project names go like C, E, G, H, I, J, K, and L.

In 2014, they will release the Luna (which starts with L) version. Between 2006 and now, they released Europa (E), Ganymede (G), Galileo (G), Helios (H), Indigo (I), Juno (J), and Kepler (K).

In the following section, we will set up Eclipse and execute our first JUnit test.

Setting up Eclipse

You can skip this section if you know how to install Eclipse and add JUnit JAR to the classpath project. The following are the steps to set up Eclipse:

  1. Visit http://www.eclipse.org/downloads/. From the dropdown, select the operating system—Windows, Mac, or Linux—and then click on the hardware architecture hyperlink, that is, 32 Bit or 64 Bit, and download the binary, as shown in the following screenshot:

  2. Extract the binary and launch Eclipse, for example, click on Eclipse.exe in Windows to launch Eclipse.

  3. Create a new workspace (for example, in Windows, enter C:\dev\junit or in Linux or Mac enter /user/local/junit; Eclipse will create the directories). Once the workspace is open, press Ctrl + N or navigate to File | New; it will open a wizard. Select Java Project and click on Next. Enter JUnitTests as the project name and click on Finish. This will create a Java project named JUnitTests.

  4. Download the junit.jar and hamcrest-core.jar packages from https://github.com/junit-team/junit/wiki/Download-and-Install and copy the jars to the JUnitTests project folder.

  5. You can add the JAR to the classpath project in two ways; either right-click on both JAR, select Build Path, and then click on Add to build path. Or, right-click on the project and select the Properties menu item. Click on Java build path on the left-hand side and open the Libraries tab. Then, click on the Add JARs... button, and it will open a pop-up window. Expand the JUnitTests project from the pop up, select the two JAR (junit.jar and hamcrest-core.jar), and add them to Libraries. We are now ready with the Eclipse setup.

Running the first unit test

JUnit 4 is an annotation-based framework. It doesn't force you to extend the TestCase class. Any Java class can act as a test. In this section, we will uncover the JUnit 4 annotations, assertions, and exceptions.

We will examine the annotations before writing our first test.

Exploring annotations

The @Test annotation represents a test. Any public method can be annotated with the@Test annotation with @Test to make it a test method. There's no need to start the method name with test.

We need data to verify a piece of code. For example, if a method takes a list of students and sorts them based on the marks obtained, then we have to build a list of students to test the method. This is called data setup. To perform the data setup, JUnit 3 defines a setUp()method in the TestCase class. A test class can override the setUp() method. The method signature is as follows:

protected void setUp() throws Exception

JUnit 4 provides a @Before annotation. If we annotate any public void method of any name with @Before, then that method gets executed before every test execution.

Similarly, any method annotated with @After gets executed after each test method execution. JUnit 3 has a tearDown() method for this purpose.

JUnit 4 provides two more annotations: @BeforeClass and @AfterClass. They are executed only once per test class. The @BeforeClass and @AfterClass annotations can be used with any public static void methods. The @BeforeClass annotation is executed before the first test and the @AfterClass annotation is executed after the last test. The following example explains the annotation usage and the execution sequence of the annotated methods.

Let's write our first test by performing the following steps:

  1. We will create a test class under a test source package. Create a Source folder named test and create a SanityTest.java Java class under package com.packtpub.junit.recap.

    It is a good practice to create test classes with a Test suffix. So, a MyClass class will have a MyClassTest test class. Some code coverage tools ignore tests if they don't end with a Test suffix.

  2. Add the following code to the SanityTest class:

    import org.junit.After;
    import org.junit.AfterClass;
    import org.junit.Before;
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    public class SanityTest {
      
      @BeforeClass
      public static void beforeClass() {
        System.out.println("***Before Class is invoked");
      }
    
      @Before
      public void before() {
        System.out.println("____________________");
        System.out.println("\t Before is invoked");
      }
      @After
      public void after() {
        System.out.println("\t After is invoked");
        System.out.println("=================");
      }
      
      @Test
      public void someTest() {
        System.out.println("\t\t someTest is invoked");
      }
      
      @Test
      public void someTest2() {
        System.out.println("\t\t someTest2 is invoked");
      }
      
      
      @AfterClass
      public static void afterClass() {
        System.out.println("***After Class is invoked");
      }
    }

    Tip

    Downloading the example code

    You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

    In the preceding class, we created six methods. Two test methods are annotated with @Test. Note that two methods (beforeClass and afterClass) are static and the other four are nonstatic. A static method annotated with @BeforeClass is invoked only once, that is, before the test class is instantiated, and @AfterClass is invoked after the class is done with all the execution.

  3. Run the test. Press Alt + Shift + X and T or navigate to Run | Run As | JUnit Test. You will see the following console (System.out.println) output:

    Check whether the before and after methods are executed before and after every test run. However, the order of the test method execution may vary. In some runs, someTest may be executed before someTest2 or vice versa. The afterClass and beforeClass methods are executed only once.

Congratulations! We successfully ran our first JUnit 4 test.

Note

@Before and @After can be applied to any public void methods. @AfterClass and @BeforeClass can be applied to only public static void methods.

Verifying test conditions with Assertion

Assertion is a tool (a predicate) used to verify a programming assumption (expectation) with an actual outcome of a program implementation; for example, a programmer can expect that the addition of two positive numbers will result in a positive number. So, he or she can write a program to add two numbers and assert the expected result with the actual result.

The org.junit.Assert package provides static overloaded methods to assert expected and actual values for all primitive types, objects, and arrays.

The following are the useful assert methods:

  • assertTrue(condition) or assertTrue(failure message, condition): If the condition becomes false, the assertion fails and AssertionError is thrown. When a failure message is passed, the failure message is thrown.

  • assertFalse(condition) or assertFalse(failure message, condition): If the condition becomes true, the assertion fails and AssertionError is thrown.

  • assertNull: This checks whether the object is null, otherwise throws AssertionError if the argument is not null.

  • assertNotNull: This checks whether the argument is not null; otherwise, it throws AssertionError.

  • assertEquals(string message, object expected, object actual), or assertEquals(object expected, object actual), or assertEquals(primitive expected, primitive actual): This method exhibits an interesting behavior if primitive values are passed and then the values are compared. If objects are passed, then the equals() method is invoked. Moreover, if the actual value doesn't match the expected value, AssertionError is thrown.

  • assertSame(object expected, object actual): This supports only objects and checks the object reference using the == operator. If two different objects are passed, then AssertionError is thrown.

  • assertNotSame: This is just the opposite of assertSame. It fails when the two argument references are the same.

    Note

    Sometimes double can lead to surprising results due to the representation that Java uses to store doubles. Any operation on a double value can lead to an unexpected result. Assert doesn't rely on double comparison; so, assertEquals(double expected, double actual) is deprecated.

    Declare a double variable sum = .999+ .98. The sum variable should add the values and store 1.98, but when you print the value in your machine, you will get 1.9889999999999999 as the output. So, if you assert sum with a double value 1.98, the test will fail.

    The assert method provides an overloaded method for the double value assertion, that is, assertEquals(double expected, double actual, double delta). During comparison, if the difference between the expected and the actual value is less than the delta value, the result is considered passed.

    For monetary calculations, it is recommended to use BigDecimal instead of doubles.

We will use the assert methods in the test as follows:

  1. Create a AssertTest test class under com.packtpub.junit.recap. Add the following lines to the class:

    package com.packtpub.junit.recap;
    
    import org.junit.Assert;
    import org.junit.Test;
    
    public class AssertTest {
    
      @Test
      public void assertTrueAndFalseTest() throws Exception {
        Assert.assertTrue(true);
        Assert.assertFalse(false);
      }
      
      @Test
      public void assertNullAndNotNullTest() throws Exception {
        Object myObject = null;
        Assert.assertNull(myObject);
        
        myObject = new String("Some value");
        Assert.assertNotNull(myObject);
      }
    }

    In the preceding code, assertTrueAndFalseTest sends true to assertTrue and false to assertFalse. So, the test should not fail.

    In assertNullAndNotNullTest, we are passing null to assertNull and a non-null String to assertNotNull; so, this test should not fail.

    Run the tests. They should be green.

  2. We will examine assertEquals and add the following test and static import the assertEquals method:

      import static org.junit.Assert.assertEquals;
    
      @Test
      public void assertEqualsTest() throws Exception {
        Integer i = new Integer("5");
        Integer j = new Integer("5");;
        assertEquals(i,j);
      }

    In the preceding code, we defined two Integer objects, i and j, and they are initialized with 5. Now, when we pass them to assertEquals, the test passes, as the assertEquals method calls i.equals(j) and not i == j. Hence, only the values are compared, not the references.

    The assertEquals method works on all primitive types and objects. To verify a double value, either use the overloaded assertEquals(actual, expected, delta) method or just use BigDecimal instead of using Double.

  3. Add a test to verify the assertNotSame behavior and static import the assertNotSame method:

       import static org.junit.Assert.assertNotSame;
      @Test
      public void assertNotSameTest() throws Exception {
        Integer i = new Integer("5");
        Integer j = new Integer("5");;
        assertNotSame(i , j);
      }

    The assertNotSame method fails only when the expected object and the actual object refers to the same memory location. Here, i and j hold the same value but the memory references are different.

  4. Add a test to verify the assertSame behavior and static import the assertSame method:

      import static org.junit.Assert.assertSame;
      @Test
      public void assertSameTest() throws Exception {
        Integer i = new Integer("5");
        Integer j = i;
        assertSame(i,j);
      }

    The assertSame method passes only when the expected object and the actual object refer to the same memory location. Here, i and j hold the same value and refer to the same location.

Working with exception handling

To test an error condition, exception handling feature is important. For example, an API needs three objects; if any argument is null, then the API should throw an exception. This can be easily tested. If the API doesn't throw an exception, the test will fail.

The @Test annotation takes the expected=<<Exception class name>>.class argument.

If the expected exception class doesn't match the exception thrown from the code, the test fails. Consider the following code:

      @Test(expected=RuntimeException.class)
  public void exception() {
    throw new RuntimeException();
  }

This is only one solution. There are several other methods that are generally considered to be better solutions. Utilizing @Rule in JUnit 4.8+ and assigning ExpectedException is a stronger solution because you can inspect the message as well as the type. We have covered @Rule in the Working with JUnit 4++ section of this chapter.

Exploring the @RunWith annotation

Test runners execute the JUnit tests. Eclipse has a built-in native graphical runner. JUnit 4 provides tools to define the suite to be run and to display its results.

When a class is annotated with @RunWith or the class extends a class annotated with @RunWith, JUnit will invoke the class that it references to run the tests on that class, instead of using the built-in runner. The @RunWith annotation is used to change the nature of the test class. It can be used to run a test as a parameterized test or even a Spring test, or it can be a Mockito runner to initialize the mock objects annotated with a @Mock annotation.

The @RunWith annotation takes an argument. The argument must be a class extended from org.junit.runner.Runner.

JUnit4.class is an example of a runner. This class aliases the current default JUnit 4 class runner.

Suite is a standard runner that allows us to build a suite that contains tests from many packages. The following is an example of @RunWith:

@RunWith(Suite.class)
public class Assumption {
   
}
 

Working with JUnit 4++


This section explores the advanced features of the JUnit 4 framework and includes the following topics: parameterized test, Hamcrest matchers and assertThat, assumption, theory, timeout, categories, rules, test suites, and tests order.

Ignoring a test

Suppose a failing test blocks you to check-in a mission critical code, and you come to know that the owner of the code is on a vacation. What do you do? You try to fix the test or just comment out or delete the test to proceed with your check-in (committing files to a source control such as SVN), or you wait until the test is fixed.

Sometimes we comment out tests because the feature is not developed. JUnit came up with a solution for this. Instead of commenting a test, we can just ignore it by annotating the test method with @Ignore. Commenting out a test or code is bad as it does nothing but increases the code size and reduces its readability. Also, when you comment out a test, then the test report doesn't tell you anything about the commented-out test; however, if you ignore a test, then the test report will tell you that something needs to be fixed as some tests are ignored. So, you can keep track of the ignored test.

Use @Ignore("Reason: why do you want to ignore?"). Giving a proper description explains the intention behind ignoring the test. The following is an example of, where a test method is ignored because the holiday calculation is not working:

@Test
@Ignore("John's holiday stuff failing")
public void when_today_is_holiday_then_stop_alarm() {
}

The following is a screenshot from Eclipse:

You can place the @Ignore annotation on a test class, effectively ignoring all the contained tests.

Executing tests in order

JUnit was designed to allow execution in a random order, but typically they are executed in a linear fashion and the order is not guaranteed. The JUnit runner depends on reflection to execute the tests. Usually, the test execution order doesn't vary from run to run; actually, the randomness is environment-specific and varies from JVM to JVM. So, it's better that you never assume they'll be executed in the same order and depend on other tests, but sometimes we need to depend on the order.

For example, when you want to write slow tests to insert a row into a database, then first update the row and finally delete the row. Here, unless the insert function is executed, delete or update functions cannot run.

JUnit 4.11 provides us with an @FixMethodOrder annotation to specify the execution order. It takes enum MethodSorters.

To change the execution order, annotate your test class using @FixMethodOrder and specify one of the following available enum MethodSorters constant:

  • MethodSorters.JVM: This leaves the test methods in the order returned by the JVM. This order may vary from run to run.

  • MethodSorters.NAME_ASCENDING: This sorts the test methods by the method name in the lexicographic order.

  • MethodSorters.DEFAULT: This is the default value that doesn't guarantee the execution order.

We will write a few tests to verify this behavior.

Add a TestExecutionOrder test and create tests, as shown in the following code snippet:

public class TestExecutionOrder {
  @Test   public void edit() throws Exception {
    System.out.println("edit executed");
  }
  @Test   public void create() throws Exception {
    System.out.println("create executed");
  }
  @Test   public void remove() throws Exception {
    System.out.println("remove executed");
  }  
}

Run the tests. The execution order may vary, but if we annotate the class with @FixMethodOrder(MethodSorters.NAME_ASCENDING), the tests will be executed in the ascending order as follows:

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestExecutionOrder { … }

The following Eclipse screenshot displays the test execution in the ascending order:

Learning assumptions

In multisite projects, sporadically, a date or time zone tests fail in a local CI server but run fine in other servers in a different time zone. We can choose to not run those automatic tests in our local server.

Sometimes our tests fail due to a bug in a third-party code or external software, but we know that after some specific build or version, the bug will be fixed. Should we comment out the code and wait until the build is available?

In many projects, Jenkins (for test automation) and SONAR (for code-quality metrics) run in a server. It has been observed that due to low resources, the automatic tests run forever when SONAR is processing and the tests run simultaneously.

JUnit has the answer to all these issues. It recommends using an org.junit.Assume class.

Like Assert, Assume offers many static methods, such as assumeTrue(condition), assumeFalse(condition), assumeNotNull(condition), and assumeThat(condition). Before executing a test, we can check our assumption using the assumeXXX methods. If our assumption fails, then the assumeXXX methods throw AssumptionViolatedException, and the JUnit runner ignores the tests with failing assumptions.

So, basically, if our assumption is not true, the tests are just ignored. We can assume that the tests are run in the EST time zone; if the tests are run somewhere else, they will be ignored automatically. Similarly, we can assume that the third-party code version is higher than the build/version 123; if the build version is lower, the tests will be ignored.

Let's write the code to validate our assumption about Assume.

Here, we will try to solve the SONAR server issue. We will assume that SONAR is not running. If SONAR runs during the test execution, the assumption will fail and the tests will be ignored.

Create an Assumption test class. The following is the body of the class:

public class Assumption {
   
  boolean isSonarRunning = false;
  @Test 
  public void very_critical_test() throws Exception {
    isSonarRunning = true;
    Assume.assumeFalse(isSonarRunning);
    assertTrue(true);
  }
  
}

Here, for simplicity, we added a isSonarRunning variable to replicate a SONAR server facade. In the actual code, we can call an API to get the value. We will set the variable to false. Then, in the test, we will reset the value to true. This means SONAR is running. So, our assumption that SONAR is not running is false; hence, the test will be ignored.

The following screenshot shows that the test is ignored. We didn't annotate the test using @Ignore:

When we change the value of the isSonarRunning variable to false, as given in the following code snippet, the test will be executed:

public void very_critical_test() throws Exception {
    isSonarRunning = false;
    Assume.assumeFalse(isSonarRunning);
    assertTrue(true);
}

Continuous integration tools such as Jenkins can run multiple tools such as Sonar to acquire code-quality metrics. It's always a good practice to have a build pipeline where the code quality is only checked after the tests pass. This prevents the CPU-intensive tasks from occurring at the same time.

Assumption is also used in the @Before methods, but be careful not to overuse it. Assumption is good for use with TDD where one writes pretests ahead of time.

Exploring the test suite

To run multiple test cases, JUnit 4 provides Suite.class and the @Suite.SuiteClasses annotation. This annotation takes an array (comma separated) of test classes.

Create a TestSuite class and annotate the class with @RunWith(Suite.class). This annotation will force Eclipse to use the suite runner.

Next, annotate the class with @Suite.SuiteClasses({ AssertTest.class, TestExecutionOrder.class, Assumption.class }) and pass comma-separated test class names.

The following is the code snippet:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({ AssertTest.class, TestExecutionOrder.class,Assumption.class })
public class TestSuite {

}

During execution, the suite will execute all the tests. The following is a screenshot of the suite run. Check whether it runs seven tests out of the three test fixtures: AssertTest, TestExecutionOrder, and Assumption.

A test suite is created for group-related tests such as a group of data access, API usage tests, or a group of input validation logic tests.

Asserting with assertThat

Joe Walnes created the assertThat(Object actual, Matcher matcher) method. General consensus is that assertThat is readable and more useful than assertEquals. The syntax of the assertThat method is as follows:

  public static void assertThat(Object actual, Matcher matcher

Here, Object is the actual value received and Matcher is an implementation of the org.hamcrest.Matcher interface. This interface comes from a separate library called hamcrest.jar.

A matcher enables a partial or an exact match for an expectation, whereas assertEquals uses an exact match. Matcher provides utility methods such as is, either, or, not , and hasItem. The Matcher methods use the builder pattern so that we can combine one or more matchers to build a composite matcher chain. Just like StringBuilder, it builds a string in multiple steps.

The following are a few examples of matchers and assertThat:

  • assertThat(calculatedTax, is(not(thirtyPercent)) );

  • assertThat(phdStudentList, hasItem(DrJohn) );

  • assertThat(manchesterUnitedClub, both( is(EPL_Champion)).and(is(UEFA_Champions_League_Champion)) );

The preceding examples are more English than a JUnit test code. So, anyone can understand the intent of the code and test, and a matcher improves readability.

Hamcrest provides a utility matcher class called org.hamcrest.CoreMatchers.

A few utility methods of CoreMatchers are allOf, anyOf, both, either, describedAs, everyItem, is, isA, anything, hasItem, hasItems, equalTo, any, instanceOf, not, nullValue, notNullValue, sameInstance, theInstance ,startsWith, endsWith, and containsString. All these methods return a matcher.

We worked with assertEquals; so, let's start with equalTo. The equalTo method is equivalent to assertEquals.

Comparing matchers – equalTo, is, and not

Create a AssertThatTest.java JUnit test and static import org.hamcrest.CoreMatchers.*; as follows:

import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class AssertThatTest {

  @Test
  public void verify_Matcher() throws Exception {
    int age = 30;
    assertThat(age, equalTo(30));
    assertThat(age, is(30));
    
    assertThat(age, not(equalTo(33)));
    assertThat(age, is(not(33)));
  }
}

Set the age variable to 30 and then likewise for assertEquals and call equalTo, which here is Matcher. The equalTo method takes a value. If the Matcher value doesn't match the actual value, then assertThat throws an AssertionError exception.

Set the age variable value to 29 and rerun the test. The following error will occur:

The is(a) attribute takes a value and returns a Boolean and behaves similar to equalTo(a). The is(a) attribute is the same as is(equalTo(a)).

The not attribute takes a value or a matcher. In the preceding code, we used assertThat(age, is(not(33)));. This expression is nothing but age is not 33 and is more readable than the assert methods.

Working with compound value matchers – either, both, anyOf, allOf, and not

In this section, we will use either, both, anyOf, allOf, and not. Add the following test to the AssertThatTest.java file:

@Test
  public void verify_multiple_values() throws Exception {
        
    double marks = 100.00;
    assertThat(marks, either(is(100.00)).or(is(90.9)));
    
    assertThat(marks, both(not(99.99)).and(not(60.00)));
    
    assertThat(marks, anyOf(is(100.00),is(1.00),is(55.00),is(88.00),is(67.8)));
    
    assertThat(marks, not(anyOf(is(0.00),is(200.00))));
    
    assertThat(marks, not(allOf(is(1.00),is(100.00), is(30.00))));
  }

In the preceding example, a marks double variable is initialized with a value of 100.00. This variable value is asserted with an either matcher.

Basically, using either, we can compare two values against an actual or calculated value. If any of them match, then the assertion is passed. If none of them match, then AssertionError is thrown.

The either(Matcher) method takes a matcher and returns a CombinableEitherMatcher class. The CombinableEitherMatcher class has a or(Matcher other) method so that either and or can be combined.

The or(Matcher other) method is translated to return (new CombinableMatcher(first)).or(other); and finally to new CombinableMatcher(new AnyOf(templatedListWith(other)));.

Using both, we can compare two values against an actual or calculated value. If any of them don't match, then the AssertionError exception is thrown. If both of them match, then the assertion is passed.

A numeric value such as a math score cannot be equal to both 60 and 80. However, we can negate the expression. If the math score is 80, then using the both matcher we can write the expression as assertThat (mathScore , both (not(60)). and(not (90))).

The anyOf matcher is more like either with multiple values. Using anyOf, we can compare multiple values against an actual or calculated value. If any of them match, then the assertion is passed. If none of them match, then the AssertionError exception is thrown.

The allOf matcher is more like both with multiple values. Using allOf, we can compare multiple values against an actual or calculated value. If any of them don't match, then the AssertionError exception is thrown. Similar to both, we can use allOf along with not to check whether a value does or doesn't belong to a set.

In the preceding example, using allOf and not, we checked whether the marks attribute is not 1, 100, or 30.

Working with collection matchers – hasItem and hasItems

In the previous section, we asserted a value against multiple values. In this section, we will assert a collection of values against a value or numerous values.

Consider the following example. A salary list is populated with three values: 50.00, 200.00, and 500.00. Use hasItem to check whether a value exists in a collection, and use hasItems to check whether multiple values exist in a collection, as shown in the following code:

   @Test
  public void verify_collection_values() throws Exception {
        
    List<Double> salary =Arrays.asList(50.0, 200.0, 500.0);
    
    assertThat(salary, hasItem(50.00));
    assertThat(salary, hasItems(50.00, 200.00));
        assertThat(salary, not(hasItem(1.00)));
  }

The hasItem matcher has two versions: one takes a value and the other takes a matcher. So, we can check a value in a collection using hasItem, or check whether a value doesn't exist in a collection using not and hasItem. The hasItems matcher operates on a set of values.

Exploring string matchers – startsWith, endsWith, and containsString

In this section, we will explore the string matchers. CoreMatchers has three built-in string matcher methods. In the following example, a String variable name is assigned a value and then we assert that the name starts with a specific value, contains a value, and ends with a value:

@Test
  public void verify_Strings() throws Exception {
    String name = "John Jr Dale";
    assertThat(name, startsWith("John"));
    assertThat(name, endsWith("Dale"));
    assertThat(name, containsString("Jr"));
  }

The startsWith matcher operates on string only. It checks whether the string starts with the given string. The endsWith matcher checks whether the string ends with the given string. The containsString matcher checks whether the string contains another string.

Sometimes, a method calls to return a JSON response. Using containsString, a specific value can be asserted.

Note

Note that startsWith, endsWith, and containsStrings are not the only string matchers. Other built-in matchers such as both, either, anyOf, and so on, can be applied to a String object.

Exploring built-in matchers

JUnitMatchers has built-in matcher methods, but all of these methods are deprecated. Use Hamcrest matchers instead of using JUnitMatchers.

Building a custom matcher

We can build our own matchers to use in assertThat. How about building a matcher that will compare two values and return true only if the actual object is less than or equal to the expected value?

Call it a lessThanOrEqual matcher. It should be allowed to use with any object that can be compared so that we can use an Integer or Double or String type or any custom class that implements the Comparable interface.

For example, assertThat(100, lessThanOrEqual(200)) should pass, but assertThat(100, lessThanOrEqual(50)) should fail and assertThat("john123", lessThanOrEqual("john123")) should pass, but assertThat("john123", lessThanOrEqual("john12")) should fail.

Follow the ensuing steps to build the lessThanOrEqual matcher:

  1. Create a LessThanOrEqual class under the com.packtpub.junit.recap package.

  2. To build a custom matcher, a class should implement the Matcher interface. However, Hamcrest recommends extending org.hamcrest.BaseMatcher rather than implementing the Matcher interface. So, we will extend BaseMatcher. The BaseMatcher class is an abstract class, and it doesn't implement describeTo(Description description) and matches(Object t).

    The public boolean matches(Object obj) method is invoked by assertThat. If this method returns false, then an AssertionError exception is thrown.

    The public void describeTo(Description description) method is invoked when matches(Object obj) returns false. This method builds the description of an expectation.

    The following code snippet explains how assertThat works:

      if(!matcher.matches(actual)){
             Description description = new StringDescription();
             description.appendText(reason).appendText("\nExpected: ).appendDescriptionOf(matcher).appendText("\n   but: ");
               
             matcher.describeMismatch(actual, description);
             throw new AssertionError(description.toString());
      }

    Note that when matcher.matches() returns false, the description is built from the actual value and the matcher. The appendDescriptionOf() method calls the describeTo() method of the matcher to build the error message.

    Finally, matcher.describeMismatch(actual, description) appends the string but: was <<actual>>.

  3. The lessThanOrEqual class needs to compare two objects, so the Matcher class should be operated on the Comparable objects. Create a generic class that operates with any type that implements the Comparable interface, as follows:

    public class LessThanOrEqual<T extends Comparable<T>> extends BaseMatcher<Comparable<T>> {
    
    }
  4. Now we need to implement the describeTo and matches methods. The assertThat method will pass the actual value to the matcher's matches(Object o) method, and lessThanOrEqual will accept a value to compare with the actual. So, in the matches method, we need two comparable objects: one passed as a parameter and the other passed to a matcher object. The expected value is passed during the matcher object instantiation as follows:

    assertThat (actual, matcher(expectedValue)).

    We will store the expectedValue during the Matcher object creation and use it in the matches() method to compare the expectedValue with the actual as follows:

    public class LessThanOrEqual<T extends Comparable<T>> extends BaseMatcher<Comparable<T>> {
      private final Comparable<T> expectedValue;
      
      public LessThanOrEqual(T expectedValue) {
       this.expectedValue = expectedValue;
      }
    
    
      @Override
      public void describeTo(Description description) {
        description.appendText(" less than or equal(<=) "+expectedValue);
      }
    
    
      @Override
      public boolean matches(Object t) {
        int compareTo = expectedValue.compareTo((T)t);
        return compareTo > -1;
      }
    }

    The preceding LessThanOrEqual class should return true only if expectedValue.compareTo(actual) >= 0 and then the describeTo() method appends the string "less than or equals (<=) "+ expectedValue text to the description, so that if the assertion fails, then the "less than or equals (<=) "+ expectedValue message will be shown.

  5. The assertThat method takes a matcher but new LessThanOrEqual(expectedValue) doesn't look good. We will create a static method in the LessThanOrEqual class to create a new object of LessThanOrEqual. Call this method from the assertThat method as follows:

      @Factory
      public static<T extends Comparable<T>>  Matcher<T>        
        lessThanOrEqual(T t) {
        return new LessThanOrEqual(t);
        }

    The @Factory annotation isn't necessary but needed for a Hamcrest tool. When we create many custom matchers, then it becomes annoying to import them all individually. Hamcrest ships with a org.hamcrest.generator.config.XmlConfigurator command-line tool that picks up predicates annotated with the @Factory annotation and collects them in a Matcher class for easy importing.

  6. Static import the LessThanOrEqual class and add a test to AssertThatTest.java to validate the custom matcher, as shown in the following code:

       @Test
      public void lessthanOrEquals_custom_matcher() throws Exception
      {
        int actualGoalScored = 2;
        assertThat(actualGoalScored, lessThanOrEqual(4));
        assertThat(actualGoalScored, lessThanOrEqual(2));
        
        double originalPI = 3.14;
        assertThat(originalPI, lessThanOrEqual(9.00));
    
        String authorName = "Sujoy";
        assertThat(authorName, lessThanOrEqual("Zachary"));
      }

    This test should pass.

  7. How about testing the code with a greater value? In Java, Integer.MAX_VALUE holds the maximum integer value and Integer.MIN_VALUE holds the minimum integer value. If we expect that the maximum value will be greater than or equal to the minimum value, then the assertion should fail. Consider the following code snippet:

        int maxInt = Integer.MAX_VALUE;
        assertThat(maxInt, lessThanOrEqual(Integer.MIN_VALUE));

    This will throw the following error:

Creating parameterized tests

Parameterized tests are used for multiple iterations over a single input to stress the object in test. The primary reason is to reduce the amount of test code.

In TDD, the code is written to satisfy a failing test. The production code logic is built from a set of test cases and different input values. For example, if we need to build a class that will return the factorial of a number, then we will pass different sets of data and verify that our implementation passes the validation.

We know that the factorial of 0 is 1, the factorial of 1 is 1, the factorial of 2 is 2, the factorial of 3 is 6, the factorial of 4 is 24, and so on.

So, if we write tests such as factorial_of_1_is_1 and factorial_of_4_is_24, then the test class will be polluted very easily. How many methods will we write?

We can create two arrays: one with the expected values and the other with the original numbers. Then, we can loop through the arrays and assert the result. We don't have to do this because the JUnit 4 framework provides us with a similar solution. It gives us a Parameterized runner.

We read about the @RunWith annotation in the preceding section. Parameterized is a special type of runner and can be used with the @RunWith annotation.

Parameterized comes with two flavors: constructor and method.

Working with parameterized constructors

Perform the following steps to build a parameterized test with a constructor:

  1. Create a source folder src and add a Factorial.java class under src/ com.packtpub.junit.recap.

  2. Implement the factorial algorithm. Add the following code to the Factorial.java class:

    package com.packtpub.junit.recap;
    
    public class Factorial {
    
      public long factorial(long number) {
        if(number == 0) {
          return 1;
        }
        
        return number*factorial(number-1);
      }
    }
  3. Add a ParameterizedFactorialTest.java test under test/ com.packtpub.junit.recap and annotate the class with @RunWith(Parameterized.class) as follows:

    import org.junit.runner.RunWith;
    import org.junit.runners.Parameterized;
    
    @RunWith(Parameterized.class)
    public class ParameterizedFactorialTest {
    
    }
  4. Add a method to create a dataset for factorial algorithm. The method should return Collection of the Object[] method. We need a collection of two dimensional arrays to hold the numbers and factorial values. To define the data parameters, annotate the method with @Parameters.

    The following code snippet defines a @parameters method factorialData():

      @Parameters
      public static Collection<Object[]> factorialData() {
        return Arrays.asList(new Object[][] {
          
          { 0, 1 }, { 1, 1 }, { 2, 2 }, { 3, 6 }, { 4, 24 }, { 5, 120 },{ 6, 720 }  
        });
      }

    Check whether the arrays hold the number and the expected factorial result (0's factorial is 1, 5's factorial is 120, and so on).

  5. The Parameterized runner needs a constructor to pass the collection of data. For each row in the collection, the 0th array element will be passed as the 1st constructor argument, the next index will be passed as 2nd argument, and so on, as follows:

        private int number;
        private int expectedResult;
    
        public ParameterizedFactorialTest(int input, int expected) {
            number= input;
            expectedResult= expected;
        }

    In the test class, we added two members to hold the number and the expected factorial value. In the constructor, set these values. The Parameterized runner will loop through the data collection (annotated with a @Parameters annotation) and pass the values to the constructor.

    For example, it will pass 0 as input and 1 as expected, then 1 as input and 1 as expected, and so on.

  6. Now, we need to add a test method to assert the number and the factorial as follows:

       @Test
      public void factorial() throws Exception {
        Factorial fact = new Factorial();
        assertEquals(fact.factorial(number),expectedResult);
      }

    We created a Factorial object and passed the number to get the actual result and then asserted the actual value with expectedResult. Here, the runner will create seven instances of the test class and execute the test method.

    The following screenshot shows the result of the test run taken from Eclipse:

    Note that the seven tests run and the tests names are [0] factorial[0], [1] factorial[1], and so on till [6].

    Note

    If the dataset returns an empty collection, the test doesn't fail; actually, nothing happens.

    If the number of parameters in the object array and the constructor argument don't match, then a java.lang.IllegalArgumentException: wrong number of arguments exception is thrown. For example, { 0, 1, 3 } will throw an exception as 3 arguments are passed, but constructor can accept only 2.

    If the constructor is not defined but the data set contains a value, then the java.lang.IllegalArgumentException: wrong number of arguments exception is thrown.

Working with parameterized methods

We learned about the parameterized constructor; now we will run the parameterized test excluding the constructor. Follow the ensuing steps to run the test using the @Parameter annotation:

  1. Add a ParameterizeParamFactorialTest.java test class.

  2. Copy the content from the constructor test and delete the constructor. Change the class members to public, as follows:

    @RunWith(Parameterized.class)
    public class ParameterizeParamFactorialTest {
    
      @Parameters
      public static Collection<Object[]> factorialData() {
        return Arrays.asList(new Object[][] {
          
          { 0, 1 }, { 1, 1 }, { 2, 2 }, { 3, 6 }, { 4, 24 }, { 5, 120 },{ 6, 720 }  
        });
      }
      
      public int number;
      public int expectedResult;
    
      
      @Test
      public void factorial() throws Exception {
        Factorial fact = new Factorial();
      assertEquals(fact.factorial(number),expectedResult);
      }
    }
  3. If we run the test, it will fail as the reflection process won't find the matching constructor. JUnit provides an annotation to loop through the dataset and set the values to the class members. @Parameter(value=index) takes a value. The value is the array index of the data collection object array. Make sure that the number and expectedResult variables are public; otherwise, the security exception will be thrown. Annotate them with the following parameters:

        @Parameter(value=0)
        public int number;
        @Parameter(value=1)
        public int expectedResult;

    Here, for each row in the data collection, the number variable will hold the 0th index of the array and the expectedResult variable will hold the 1st index.

  4. Run the test; seven tests will be executed.

Giving a name

In the constructor example, we found that the test names are assigned with indexes such as [0], [1], and so on. So, if a test fails, then it is not easy to identify the data. To identify an individual test case in a parameterized test, a name is required. The @Parameters annotation allows placeholders that are replaced at runtime, and we can use them. The following are the placeholders:

  • {index}: This represents the current parameter index

  • {0}, {1},…: This represents the first, second, and so on, parameter values

The following code snippet annotates the dataset with the name placeholders:

  @Parameters(name = "{index}: factorial({0})={1}")
    public static Collection<Object[]> factorialData() {
      return Arrays.asList(new Object[][] {
      
        { 0, 1 }, { 1, 1 }, { 2, 2 }, { 3, 6 }, { 4, 24 }, { 5, 120 },{ 6, 720 }  
      });
    }

Eclipse has a bug that chops off the name.

Working with timeouts

JUnit tests are automated to get quick feedback after a change in the code. If a test runs for a long time, it violates the quick feedback principle. JUnit provides a timeout value (in milliseconds) in the @Test annotation to make sure that if a test runs longer than the specified value, the test fails.

The following is an example of a timeout:

  @Test(timeout=10)
  public void forEver() throws Exception {
    Thread.sleep(100000);
  }

Here, the test will fail automatically after 10 milliseconds. The following is an Eclipse screenshot that shows the error:

Exploring JUnit theories

A theory is a kind of a JUnit test but different from the typical example-based JUnit tests, where we assert a specific data set and expect a specific outcome. JUnit theories are an alternative to JUnit's parameterized tests. A JUnit theory encapsulates the tester's understanding of an object's universal behavior. This means whatever a theory asserts is expected to be true for all data sets. Theories are useful for finding bugs in boundary-value cases.

Parameterized tests allow us to write flexible data-driven tests and separate data from the test methods. Theories are similar to parameterized tests—both allow us to specify the test data outside of the test case.

Parameterized tests are good but they have the following drawbacks:

  • Parameters are declared as member variables. They pollute the test class and unnecessarily make the system complex.

  • Parameters need to be passed to the single constructor or variables need to be annotated, simply making the class incomprehensible.

  • Test data cannot be externalized.

Theory comes up with many annotations and a runner class. Let's examine the important annotations and classes in theory, as follows:

  • @Theory: Like @Test, this annotation identifies a theory test to run. The @Test annotation doesn't work with a theory runner.

  • @DataPoint: This annotation identifies a single set of test data (similar to @Parameters), that is, either a static variable or a method.

  • @DataPoints: This annotation identifies multiple sets of test data, generally an array.

  • @ParametersSuppliedBy: This annotation provides the parameters to the test cases.

  • Theories: This annotation is a JUnit runner for the theory-based test cases and extends org.junit.runners.BlockJUnit4ClassRunner.

  • ParameterSupplier: This is an abstract class that gives us the handle on the parameters that we can supply to the test case.

We will start with a simple theory and then explore more. Perform the following steps:

  1. Create a MyTheoryTest.java class and annotate the class with @RunWith(Theories.class). To run a theory, this special runner is required. Consider the following code:

    @RunWith(Theories.class)
    public class MyTheoryTest {
    
    }
  2. Now run the test. It will fail with the java.lang.Exception: No runnable methods error because no theory is defined yet. Like the @Test annotation, we will define a method and annotate it with @Theory as follows:

    @RunWith(Theories.class)
    public class MyTheoryTest {
      
      @Theory
      public void sanity() {
        System.out.println("Sanity check");
      }
    }

    Run the theory, and it will be executed with no error. So, our theory setup is ready.

  3. Define a public static String with a name variable and annotate this variable with @DataPoint. Now execute the test, nothing special happens. If a theory method (annotated with @Theory) takes an argument and a variable annotated with @DataPoint matches the type, then the variable is passed to the theory during execution. So, change the sanity method and add a String argument to pass @DataPoint to the sanity() method, as follows:

    @RunWith(Theories.class)
    public class MyTheoryTest {
      @DataPoint public static String name ="Jack";
      
      @Theory
      public void sanity(String aName) {
        System.out.println("Sanity check "+aName);
      }
    }

    Now run the theory. It will pass the @DataPoint name to the sanity(String aName) method during execution and the name will be printed to the console.

  4. Now, add another static @DataPoint, call it mike, and rename the name variable to jack, as follows:

    @RunWith(Theories.class)
    public class MyTheoryTest {
      @DataPoint public static String jack ="Jack";
      @DataPoint public static String mike ="Mike";
      
      @Theory
      public void sanity(String aName) {
        System.out.println("Sanity check "+aName);
      }
    }

    During theory execution, both the @DataPoint variables will be passed to the sanity(String aName) method. The output will be as follows:

  5. Now, slightly modify the sanity() method—rename the aName argument to firstName and add a second String argument, lastName. So now the sanity method takes the String arguments, fistName and lastName. Print these variables using the following code:

    @RunWith(Theories.class)
    public class MyTheoryTest {
      @DataPoint public static String jack ="Jack";
      @DataPoint public static String mike ="Mike";
      
      @Theory
      public void sanity(String firstName, String lastName) {
        System.out.println("Sanity check "+firstName+", "+lastName);
      }
    }

    When executed, the output will be as follows:

    So, 2 x 2 = 4 combinations are used. When the multiple @DataPoint annotations are defined in a test, the theories apply to all possible well-typed combinations of data points for the test arguments.

  6. So far we have only examined single-dimension variables. The @DataPoints annotation is used to provide a set of data. Add a static char array to hold the character variables and add a Theory method to accept two characters. It will execute the theory with 9 (3 ^ 2) possible combinations as follows:

      @DataPoints  public static char[] chars = 
              new char[] {'A', 'B', 'C'};
      @Theory
      public void build(char c, char d) {
        System.out.println(c+" "+d);
      }

    The following is the output:

Externalizing data using @ParametersSuppliedBy and ParameterSupplier

So far, we have covered how to set up test data using @DataPoint and @DataPoints. Now, we will use external classes to supply data in our tests using @ParametersSuppliedBy and ParameterSupplier. To do this, perform the following steps:

  1. Create an Adder.java class. This class will have two overloaded add() methods to add numbers and strings. We will unit test the methods using theory.

    The following is the Adder class:

      public class Adder {
      
        public Object add(Number a, Number b) {
          return a.doubleValue()+b.doubleValue();
        }
      
        public Object add(String a, String b) {
          return a+b;
        }
      }
  2. Create an ExternalTheoryTest.java theory as follows:

    @RunWith(Theories.class)
    public class ExternalTheoryTest {
    
    }
  3. We will not use @DataPoints to create data. Instead, we will create a separate class to supply numbers to validate the add operation. JUnit provides a ParameterSupplier class for this purpose. ParameterSupplier is an abstract class, and it forces you to define a method as follows:

    public abstract List<PotentialAssignment> getValueSources(ParameterSignature parametersignature);

    PotentialAssignment is an abstract class that JUnit theories use to provide test data to test methods in a consistent manner. It has a static forValue method that you can use to get an instance of PotentialAssignment.

    Create a NumberSupplier class to supply different types of numbers: float, int, double, long, and so on. Extend the ParameterSupplier class as follows:

      import org.junit.experimental.theories.ParameterSignature;
      import org.junit.experimental.theories.ParameterSupplier;
      import org.junit.experimental.theories.PotentialAssignment;
    
      public  class NumberSupplier extends ParameterSupplier {
        @Override
          public List<PotentialAssignment>       
          getValueSources(ParameterSignature sig) {
            List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
            list.add(PotentialAssignment.forValue("long", 2L));
            list.add(PotentialAssignment.forValue("float", 5.00f));
            list.add(PotentialAssignment.forValue("double", 89d));
            return list;
      }
    
    };

    Check whether the overridden method creates a list of PotentialAssignment values of different numbers.

  4. Now, modify the theory to add two numbers. Add a theory method as follows:

    import org.junit.experimental.theories.ParametersSuppliedBy;
    import org.junit.experimental.theories.Theories;
    import org.junit.experimental.theories.Theory;
    import org.junit.runner.RunWith;
    
    @RunWith(Theories.class)
    public class ExternalTheoryTest {
    
      @Theory
      public void adds_numbers(
      @ParametersSuppliedBy(NumberSupplier.class) Number num1,
      @ParametersSuppliedBy(NumberSupplier.class) Number num2) 
      {
        System.out.println(num1 + " and " + num2);
      }
    
    }

    Check the adds_numbers method; two Number arguments num1 and num2 are annotated with @ParametersSuppliedBy(NumberSupplier.class).

    When this theory is executed, the NumberSupplier class will pass a list.

  5. Execute the theory; it will print the following result:

  6. Now, we can check our Adder functionality. Modify the theory to assert the result.

    Create an instance of the Adder class and call the add method by passing num1 and num2. Add the two numbers and assert the value with the results of Adder.

    The assertEquals(double, double) method is deprecated as the double value calculation results in an unpredictable result. So, the assert class adds another version of assertEquals for doubles; it takes three arguments: actual, expected, and a delta. If the difference between the actual and the expected value is greater than or equal to delta, then the assertion passes as follows:

    @RunWith(Theories.class)
    public class ExternalTheoryTest {
    
      @Theory
      public void adds_numbers(
      @ParametersSuppliedBy(NumberSupplier.class) Number num1,
      @ParametersSuppliedBy(NumberSupplier.class) Number num2) {
        Adder anAdder = new Adder();
        double expectedSum = num1.doubleValue()+num2.doubleValue();
        double actualResult = (Double)anAdder.add(num1, num2);
        assertEquals(actualResult, expectedSum, 0.01);
      }
    
    }

    The Adder class has an add method for String. Create a StringSupplier class to supply String values to our theory and modify the theory class to verify the add (String, String) method behavior. You can assert the Strings as follows:

    • String expected = str1+str2;

    • assertEquals(expected, actual);

    Here, str1 and str2 are the two method arguments of the theory.

Dealing with JUnit rules

Rules allow very flexible addition or redefinition of the behavior of each test method in a test class. Rules are like Aspect Oriented Programming (AOP); we can do useful things before and/or after the actual test execution. You can find more information about AOP at http://en.wikipedia.org/wiki/Aspect-oriented_programming.

We can use the inbuilt rules or define our custom rule.

In this section, we will look at the inbuilt rules and create our custom Verifier and WatchMan rule.

Playing with the timeout rule

The timeout rule applies the same timeout to all the test methods in a class. Earlier, we used the timeout in the @Test annotation as follows:

@Test(timeout=10)

The following is the syntax of the timeout rule:

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;

public class TimeoutTest {
    
    @Rule
    public Timeout globalTimeout =  new Timeout(20);
    
    @Test
    public void testInfiniteLoop1() throws InterruptedException{
      Thread.sleep(30);
    }
    
    @Test
    public void testInfiniteLoop2() throws InterruptedException{
      Thread.sleep(30);
    }
    
}

When we run this test, it times out after 20 milliseconds. Note that the timeout is applied globally to all methods.

Working with the ExpectedException rule

The ExpectedException rule is an important rule for handling exceptions. It allows you to assert the expected exception type and the exception message, for example, your code may throw a generic exception (such as IllegalStateException) for all failure conditions, but you can assert the generic exception message to verify the exact cause.

Earlier, we used @Test(expected=Exception class) to test the error conditions.

The ExpectedException rule allows in-test specification of expected exception types and messages.

The following code snippet explains how an exception rule can be used to verify the exception class and the exception message:

public class ExpectedExceptionRuleTest {

   @Rule
    public ExpectedException thrown= ExpectedException.none();

    @Test
    public void throwsNothing() {

    }

    @Test
    public void throwsNullPointerException() {
      thrown.expect(NullPointerException.class);
      throw new NullPointerException();
    }

    @Test
    public void throwsIllegalStateExceptionWithMessage() {
      thrown.expect(IllegalStateException.class);
      thrown.expectMessage("Is this a legal state?");
     
      throw new IllegalStateException("Is this a legal state?");
    }
}

The expect object sets the expected exception class and expectMessage sets the expected message in the exception. If the message or exception class doesn't match the rule's expectation, the test fails. The ExpectedException object thrown is reset on each test.

Unfolding the TemporaryFolder rule

The TemporaryFolder rule allows the creation of files and folders that are guaranteed to be deleted when the test method finishes (whether it passes or fails). Consider the following code:

@Rule
  public TemporaryFolder folder = new TemporaryFolder();

  @Test
  public void testUsingTempFolder() throws IOException {
    File createdFile = folder.newFile("myfile.txt");
    File createdFolder = folder.newFolder("mysubfolder");
    
  }

Exploring the ErrorCollector rule

The ErrorCollector rule allows the execution of a test to continue after the first problem is found (for example, to collect all the incorrect rows in a table and report them all at once) as follows:

import org.junit.rules.ErrorCollector;
import static org.hamcrest.CoreMatchers.equalTo;

public class ErrorCollectorTest {

   @Rule
   public ErrorCollector collector = new ErrorCollector();
    
   @Test
   public void fails_after_execution() {
   collector.checkThat("a", equalTo("b"));
   collector.checkThat(1, equalTo(2));
   collector.checkThat("ae", equalTo("g"));
   }
}

In this example, none of the verification passes but the test still finishes its execution, and at the end, notifies all errors.

The following is the log—the arrows indicate the errors—and also note that only one test method is being executed but Eclipse indicates three failures:

Working with the Verifier rule

Verifier is a base class of ErrorCollector, which can otherwise turn passing tests into failing tests if a verification check fails. The following example demonstrates the Verifier rule:

public class VerifierRuleTest {
  private String errorMsg = null;

  @Rule
  public TestRule rule = new Verifier() {
    protected void verify() {
      assertNull("ErrorMsg should be null after each test execution",errorMsg);
    }
  };
  
  
  @Test
  public void testName() throws Exception {
    errorMsg = "Giving a value";
  }
}

Verifier's verify method is executed after each test execution. If the verify method defines any assertions, and that assertion fails, then the test is marked as failed.

In the preceding example, the test should not fail as the test method doesn't perform any comparison; however, it still fails. It fails because the Verifier rule checks that after every test execution, the errorMsg string should be set as null, but the test method sets the value to Giving a value; hence, the verification fails.

Learning the TestWatcher rule

TestWatcher (and the deprecated TestWatchman) are base classes for rules that take note of the testing action, without modifying it. Consider the following code:

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestWatcherTest {

  private static String dog = "";

    @Rule
    public TestWatcher watchman = new TestWatcher() {
      @Override
      public Statement apply(Statement base, Description description) {
        return super.apply(base, description);
      }

      @Override
      protected void succeeded(Description description) {
        dog += description.getDisplayName() + " " + "success!\n";
      }

      @Override
      protected void failed(Throwable e, Description description) {
        dog += description.getDisplayName() + " " + e.getClass().getSimpleName() + "\n";
      }

      @Override
      protected void starting(Description description) {
        super.starting(description);
      }

      @Override
      protected void finished(Description description) {
        super.finished(description);
      }
    };

    @Test
    public void red_test() {
      fail();
    }

    @Test
    public void green() {
    }
  
    @AfterClass
    public static void afterClass() {
      System.out.println(dog);
    }
}

We created a TestWatcher class to listen to every test execution, collected the failure, and success instances, and at the end, printed the result in the afterClass() method.

The following is the error shown on the console:

green(com.packtpub.junit.recap.rule.TestWatcherTest) success!
red_test(com.packtpub.junit.recap.rule.TestWatcherTest) AssertionError

Working with the TestName rule

The TestName rule makes the current test name available inside test methods. The TestName rule can be used in conjunction with the TestWatcher rule to make a unit testing framework compile a unit testing report.

The following test snippet shows that the test name is asserted inside the test:

public class TestNameRuleTest {

  @Rule
    public TestName name = new TestName();
    
    @Test
    public void testA() {
      assertEquals("testA", name.getMethodName());
    }
    
    @Test
    public void testB() {
      assertEquals("testB", name.getMethodName());
    }
}

The following section uses the TestName rule to get the method name before test execution.

Handling external resources

Sometimes JUnit tests need to communicate with external resources such as files or databases or server sockets. Dealing with external resources is always messy because you need to set up state and tear it down later. The ExternalResource rule provides a mechanism that makes resource handling a bit more convenient.

Previously, when you had to create files in a test case or work with server sockets, you had to set up a temporary directory, or open a socket in a @Before method and later delete the file or close the server in an @After method. But now, JUnit provides a simple AOP-like mechanism called the ExternalResource rule that makes this setup and cleanup work the responsibility of the resource.

The following example demonstrates the ExternalResource capabilities. The Resource class represents an external resource and prints the output in the console:

class Resource{
  public void open() {
    System.out.println("Opened");
  }
  
  public void close() {
    System.out.println("Closed");
  }
  
  public double get() {
    return Math.random();
  }
}

The following test class creates ExternalResource and handles the resource lifecycle:

public class ExternalResourceTest {
  Resource resource;
  public @Rule TestName name = new TestName();

  public @Rule ExternalResource rule = new ExternalResource() {
    @Override protected void before() throws Throwable {
      resource = new Resource();
      resource.open();
      System.out.println(name.getMethodName());
    }
    
    @Override protected void after()  {
      resource.close();
      System.out.println("\n");
    }
  };
  
  @Test
  public void someTest() throws Exception {
    System.out.println(resource.get());
  }
  
  @Test
  public void someTest2() throws Exception {
    System.out.println(resource.get());
  }
}

The anonymous ExternalResource class overrides the before and after methods of the ExternalResource class. In the before method, it starts the resource and prints the test method name using the TestName rule. In the after method, it just closes the resource.

The following is the test run output:

Opened
someTest2
0.5872875884671511
Closed

Opened
someTest
0.395586457988541
Closed

Note that the resource is opened before test execution and closed after the test. The test name is printed using the TestName rule.

Exploring JUnit categories

The Categories runner runs only the classes and methods that are annotated with either the category given with the @IncludeCategory annotation or a subtype of that category. Either classes or interfaces can be used as categories. Subtyping works, so if you use @IncludeCategory(SuperClass.class), a test marked @Category({SubClass.class}) will be run.

We can exclude categories by using the @ExcludeCategory annotation.

We can define two interfaces using the following code:

public interface SmartTests { /* category marker */ }
public interface CrazyTests { /* category marker */ }

public class SomeTest {
  @Test
  public void a() {
    fail();
  }

  @Category(CrazyTests.class)
  @Test
  public void b() {
  }
}

@Category({CrazyTests.class, SmartTests.class})
public class OtherTest {
  @Test
  public void c() {

  }
}

@RunWith(Categories.class)
@IncludeCategory(CrazyTests.class)
@SuiteClasses( { SomeTest.class, OtherTest.class }) // Note that Categories is a kind of Suite
public class CrazyTestSuite {
  // Will run SomeTest.b and OtherTest.c, but not SomeTest.a
}

@RunWith(Categories.class)
@IncludeCategory(CrazyTests.class)
@ExcludeCategory(SmartTests.class)
@SuiteClasses( { SomeTest.class, OtherTest.class }) 
public class CrazyTestSuite {
  // Will run SomeTest.b, but not SomeTest.a or OtherTest.c
}
 

Summary


This JUnit refresher chapter covers both the basic and advanced usage of JUnit.

The basic section covers the annotation based on JUnit 4 testing, assertion, the @RunWith annotation, exception handling, and the Eclipse setup for running the JUnit tests.

The advanced section covers parameterized tests, matchers and assertThat, a custom lessThanOrEqual() matcher, assumption, theory, a custom NumberSupplier class, timeout, categories, TestName, ExpectedException, TemporaryFolder, ErrorCollector, Verifier and TestWatcher rules, test suites, and executing tests in order.

By now, you will be able to write and execute JUnit 4 tests and be familiar with the advanced concepts of JUnit 4.

Chapter 2, Automating JUnit Tests, focuses on getting you quickly started with project-building tools and test automation. It provides an overview of continuous integration, explores the incremental Gradle build and Maven build lifecycle, Ant scripting, and Jenkins automation using Gradle, Maven, and Ant scripts.

About the Author

  • Sujoy Acharya

    Sujoy Acharya works as a Principal Engineer with Cerner. While growing up, he pursued his interests in the fields of computer science and engineering. His hobbies are watching movies and sitcoms, playing outdoor sports, and reading books.

    Sujoy likes to research upcoming technologies. His major contributions are in the fields of TDD, building scalable applications, cloud services, and the Spring Framework.

    He has authored four books for Packt, namely, Test-Driven Development with Mockito, Mastering Unit Testing using Mockito and JUnit, Mockito Essentials, and Mockito for Spring.

    Browse publications by this author

Latest Reviews

(2 reviews total)
fdfdfdfadsfsdafgdsaf ghfhdfhdfs
The book's title is misleading and that is not the first time with books by Packt. I needed Mockito advice, I received a book with interesting snippets on JUnit but needed info on Mockito was lacking. E.g. I had to Google the import statements to use.

Recommended For You

Book Title
Unlock this book and the full library for FREE
Start free trial