JUnit Test Listener: JUnit 4 RunListener Example

JUnit Test Listener – JUnit RunListener Example. JUnit also provide support for adding listeners while executing the tests via RunListener class.

JUnit Test Listeners allow us to listen to the events that occur during the execution of tests. These events include the start and end of tests, test failures, test assumptions, and more. Listeners can be useful for reporting, logging, or executing custom logic when tests run.

JUnit also provides support for adding listeners while executing the tests via the RunListener class. This listener can be used for various purposes from improved logging to test specific logic.

1. JUnit RunListener Example

In JUnit 4, we can create a test listener by extending the RunListener class and overriding its method to inject the custom code.

Let’s see an example.

1.1. JUnit Test

We are writing two test classes below for example only. We will monitor the logs printed for tests written in these classes.

public class TestFeatureOne {

	@Test
	public void testFirstFeature(){
		Assert.assertTrue(true);
	}
}
public class TestFeatureTwo {

	@Test
	public void testSecondFeature() {
		Assert.assertTrue(true);
	}

	@Test
	@Ignore
	public void testSecondFeatureIngored() {
		Assert.assertTrue(true);
	}
}

1.2. JUnit Test Listener Class

Let’s write run listener. This listener will extend the RunListener class provided by JUnit.

We are free to override any number of methods RunListener class from including no method at all.

import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
 
public class ExecutionListener extends RunListener
{
  /**
   * Called before any tests have been run.
   * */
  public void testRunStarted(Description description) throws java.lang.Exception {
    System.out.println("Number of tests to execute : " + description.testCount());
  }
 
  /**
   *  Called when all tests have finished
   * */
  public void testRunFinished(Result result) throws java.lang.Exception {
    System.out.println("Number of tests executed : " + result.getRunCount());
  }
 
  /**
   *  Called when an atomic test is about to be started.
   * */
  public void testStarted(Description description) throws java.lang.Exception {
    System.out.println("Starting execution of test case : "+ description.getMethodName());
  }
 
  /**
   *  Called when an atomic test has finished, whether the test succeeds or fails.
   * */
  public void testFinished(Description description) throws java.lang.Exception {
    System.out.println("Finished execution of test case : "+ description.getMethodName());
  }
 
  /**
   *  Called when an atomic test fails.
   * */
  public void testFailure(Failure failure) throws java.lang.Exception {
    System.out.println("Execution of test case failed : "+ failure.getMessage());
  }
 
  /**
   *  Called when a test will not be run, generally because a test method is annotated with Ignore.
   * */
  public void testIgnored(Description description) throws java.lang.Exception {
    System.out.println("Execution of test case ignored : "+ description.getMethodName());
  }
}

2. Demo

Now, let’s run the tests and observe the listener output.

import org.junit.runner.JUnitCore;
import com.howtodoinjava.junit.TestFeatureOne;
import com.howtodoinjava.junit.TestFeatureTwo;
 
public class ExecuteWithRunListener {

  public static void main(String[] args)  {

    JUnitCore runner = new JUnitCore();
    runner.addListener(new ExecutionListener());
    runner.run(TestFeatureOne.class, TestFeatureTwo.class);
  }
}

Program Output.

Number of tests to execute : 3

Starting execution of test case : testFirstFeature
Finished execution of test case : testFirstFeature

Starting execution of test case : testSecondFeature
Finished execution of test case : testSecondFeature

Execution of test case ignored : testSecondFeatureIngored
Number of tests executed : 2

Clearly, adding a listener provides extra control on test execution with improved logging support.

Happy Learning !!

Leave a Comment

  1. I tried to use your example but replaced the runner.run(TestFeatureOne.class, TestFeatureTwo.class); with runner.main(args); so I could pass in the test application name and make the ExecutionListener static inside ExecuteWithRunListener so this is all in one file. The test application was run but I don’t see anything from ExecutionListener class. There were 15 tests cases but all it see is the Time: 3.572 and the OK (15 tests).

    This is my code:

    package jhuapl.fact;
    
    import org.junit.runner.JUnitCore;
    import org.junit.runner.Description;
    import org.junit.runner.Result;
    import org.junit.runner.notification.Failure;
    import org.junit.runner.notification.RunListener;
    
    public class ExecuteWithRunListener
    {
        public static class ExecutionListener extends RunListener
        {
            /**
             * Called before any tests have been run.
             * */
            public void testRunStarted(Description description) throws java.lang.Exception
            {
                System.out.println("Number of tests to execute : " + description.testCount());
            }
    
            /**
             *  Called when all tests have finished
             * */
            public void testRunFinished(Result result) throws java.lang.Exception
            {
                System.out.printf("Test ran: %s, Failed: %s, Run Time: %s ms%n",  
                   result.getRunCount(), result.getFailureCount(), result.getRunTime());
            }
    
            /**
             *  Called when an atomic test is about to be started.
             * */
            public void testStarted(Description description) throws java.lang.Exception
            {
                System.out.println("Entering test case "+ description.getMethodName());
            }
    
            /**
             *  Called when an atomic test has finished, whether the test succeeds or fails.
             * */
            public void testFinished(Description description) throws java.lang.Exception
            {
                System.out.println("Leaving test case : "+ description.getMethodName());
            }
    
            /**
             *  Called when an atomic test fails.
             * */
            public void testFailure(Failure failure) throws java.lang.Exception
            {
                System.out.printf("Test case failed ran: %s, Failed: %s%n", failure.getMessage());
            }
    
            /**
             *  Called when a test will not be run, generally because a test method is annotated with Ignore.
             * */
            public void testIgnored(Description description) throws java.lang.Exception
            {
                System.out.println("Execution of test case ignored : "+ description.getMethodName());
            }
        }
    /*
     * javac -cp .:/usr/share/java/junit.jar jhuapl.fact.ExecuteWithRunListener.java
     * java jhuapl.fact.ExecuteWithRunListener jhuapl.fact.osi.common.decider.DeciderUnitTest
     */
        public static void main(String[] args)
        {
            JUnitCore runner = new JUnitCore();
            runner.addListener(new ExecutionListener());    // Adding listener here
            System.out.println("I am calling main : " + args);
            runner.main(args);
        }
    }

    What I want to do in the end is print out the run time per test case like ant-unit does vs. this sum value for the suite. So want am I doing wrong here?

    Reply
  2. I am executing my junit tests from ant build.xml. I have created a Listener which should listen to all executing tests. How can I add this listener in my build.xml, as I don’t have ant junitcore object to execute my tests.

    Reply
  3. how and where to add run listener in an existing suite . I have some thousands of test case in an existing frame work . I want to add listen which should run at the end of test to teardown external test data created in third party system

    Reply
    • As you can see that listeners do not take input parameters which bring you some data used in testcase. so it is logically not possible for me to write a teardown method in interceptor.
      Also, writing teardown code for thousands tests in one interceptor, seems bad to me.
      Cleaning up the data should be responsibility of testcase itself, because it knows what is that it is making dirty for others. Use interceptors for logging; or report generation.

      Reply

Leave a Comment

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.