Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to share JUnit BeforeClass logic among multiple test classes

Currently, all of my JUnit tests extend from a common base class that provides methods tagged with @BeforeClass and @AfterClass annotations - all these really do is setup a bunch of static resources/services for the tests to use.

This seems a awkward to me for a few reasons:

  1. Part of the point of JUnit4 (from my understanding) is that we shouldn't need this classical test inheritance anymore.
  2. When I run these tests as part of a suite instead of individually (which we often do), the @BeforeClass and @AfterClass get invoked multiple times, slowing down the tests - we really should only be calling these once

What I'd like to do is somehow move the current BeforeClass/AfterClass logic out of the inheritance chain and into something that can be shared by individual tests and the suite as a whole.

Can this be done? If so, how? (If it matters, I'm using JUnit 4.7, and it could be a hard sell to update to a different version)

like image 894
Krease Avatar asked Dec 09 '14 23:12

Krease


People also ask

Is it possible to group the test cases in JUnit?

JUnit test suites help to grouping and executing tests in bulk. Executing tests separately for all test classes is not desired in most cases. Test suites help in achieving this grouping. In JUnit, test suites can be created and executed with these annotations.

Can we have two @before in JUnit?

The JUnit Team therefore recommends that developers declare at most one @BeforeEach method and at most one @AfterEach method per test class or test interface unless there are no dependencies between the @BeforeEach methods or between the @AfterEach methods.

What is difference between @before and @BeforeClass?

The code marked @Before is executed before each test, while @BeforeClass runs once before the entire test fixture. If your test class has ten tests, @Before code will be executed ten times, but @BeforeClass will be executed only once.


1 Answers

A solution to the first issue is to move the logic into an extension of org.junit.rules.ExternalResource hooked up to the test via a @ClassRule, introduced in JUnit 4.9:

public class MyTest {     @ClassRule     public static final TestResources res = new TestResources();     @Test     public void testFoo() {         // test logic here     } }  public class TestResources extends ExternalResource {     protected void before() {         // Setup logic that used to be in @BeforeClass     }     protected void after() {         // Setup logic that used to be in @AfterClass     } } 

In this way, the resources previously managed by the base class are moved out of the test class hierarchy and into more modular/consumable "resources" that can be created before a class runs and destroyed after a class runs.

As for solving both issues at the same time though - ie: having the same high level setup/teardown run as both part of an individual test and as part of a suite - there doesn't seem to be any specific built in support for this. However..., you could implement it yourself:

Simply change the @ClassRule resource creation into a factory pattern that does reference counting internally to determine whether or not to create/destroy the resource.

For example (please note this is rough and might need some tweaks/error handling for robustness):

public class TestResources extends ExternalResource {     private static int refCount = 0;      private static TestResources currentInstance;      public static TestResources getTestResources () {         if (refCount == 0) {             // currentInstance either hasn't been created yet, or after was called on it - create a new one             currentInstance = new TestResources();         }         return currentInstance;     }      private TestResources() {         System.out.println("TestResources construction");         // setup any instance vars     }      protected void before() {         System.out.println("TestResources before");         try {             if (refCount == 0) {                 System.out.println("Do actual TestResources init");             }         }         finally {             refCount++;         }     }      protected void after() {         System.out.println("TestResources after");         refCount--;         if (refCount == 0) {             System.out.println("Do actual TestResources destroy");         }     } } 

Both your suite / test classes would just use the resource as a @ClassResource through the factory method:

@RunWith(Suite.class) @SuiteClasses({FooTest.class, BarTest.class}) public class MySuite {     @ClassRule     public static TestResources res = TestResources.getTestResources();     @BeforeClass     public static void suiteSetup() {         System.out.println("Suite setup");     }     @AfterClass     public static void suiteTeardown() {         System.out.println("Suite teardown");     } } public class FooTest {     @ClassRule     public static TestResources res = TestResources.getTestResources();      @Test     public void testFoo() {         System.out.println("testFoo");     } } public class BarTest {     @ClassRule     public static TestResources res = TestResources.getTestResources();      @Test     public void testBar() {         System.out.println("testBar");     } } 

When running an individual test, the refcounting won't have any effect - the "actual init" and "actual teardown" will only happen once. When running through the suite, the suite will create the TestResource, and the individual tests will just reuse the already instantated one (the refcounting keeps it from being actually destroyed and recreated between tests in the suite).

like image 119
Krease Avatar answered Sep 20 '22 09:09

Krease