25

I have a class, which I use as a basis for my unit tests. In this class I initialize the whole environment for my tests, setting up database mappings, enter a number of database records across multiple tables, etc. That class has a method with a @BeforeClass annotation which does the initialization. Next thing, I extend that class with specific classes in which I have @Test methods.

My question is, since the before class is exactly the same for all these test classes, how can I ensure that they are run only once for all the tests. One simple solution is that I could keep all the tests in one class. However, the number of tests is huge, also they are categorised based on functional heads. So they are located in different classes. However since they need the exact same setup, they inherit the @BeforeClass. As a result the whole setup is done at least once per test class, taking much more time in total than I would prefer.

I could, though, put them all in various subpackages under one package, hence if there is a way, how I can run set up once for all the tests within that package, it would be great.

neoInfinite
  • 341
  • 2
  • 4
  • 12
  • 1
    Have you looked into something like this: http://stackoverflow.com/questions/6580670/testsuite-setup-in-junit-4 – efan Sep 02 '13 at 13:38
  • 1
    Known limitation. Because of such issues personally I prefer [testng](http://testng.org/doc/documentation-main.html) framework. – G. Demecki Sep 16 '14 at 09:59
  • @G.Demecki you mention the testNG framework - why didn't you create an answer with the TestNG way to do this for anyone who might wonder? This same stack overflow question for TestNG doesn't seem to exist. – Nicholas DiPiazza Apr 09 '16 at 01:34
  • @NicholasDiPiazza you are right, but it was two years ago and I no longer prefer TestNG :) Mentioned by the OP issue can be easily overcome by using a static block or by using DI container along with JUnit. – G. Demecki Apr 22 '16 at 07:50
  • yes indeed. it worked for me in my complex 500 test scenario. – Nicholas DiPiazza Apr 22 '16 at 17:56

6 Answers6

20

With JUnit4 test suite you can do something like this :

@RunWith(Suite.class)
@Suite.SuiteClasses({ Test1IT.class, Test2IT.class })
public class IntegrationTestSuite
{
    @BeforeClass
    public static void setUp()
    {
        System.out.println("Runs before all tests in the annotation above.");
    }

    @AfterClass
    public static void tearDown()
    {
        System.out.println("Runs after all tests in the annotation above.");
    }
}

Then you run this class as you would run a normal test class and it will run all of your tests.

FredBoutin
  • 392
  • 3
  • 16
8

JUnit doesn't support this, you will have to use the standard Java work-arounds for singletons: Move the common setup code into a static code block and then call an empty method in this class:

 static {
     ...init code here...
 }

 public static void init() {} // Empty method to trigger the execution of the block above

Make sure that all tests call init(), for example my putting it into a @BeforeClass method. Or put the static code block into a shared base class.

Alternatively, use a global variable:

 private static boolean initialize = true;
 public static void init() {
     if(!initialize) return;
     initialize = false;

     ...init code here...
 }
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 1
    The variable must be static, otherwise it won't work or compile at all ;-) Declaring the method itself static is not necessary. Especially when (like in tests I just came across) you need things like @Inject or other DI mechanisms, keeping a non-static init() method that calls a static helper variable works best. – Werner Keil Jul 20 '15 at 14:10
  • @WernerKeil Thanks, fixed. – Aaron Digulla Jul 21 '15 at 10:33
  • deriving all applicable tests from a BaseTest class with a static initializer block, is an excellent advice. thank you. – Shreyas Jun 28 '18 at 04:22
3

Create one base class for all tests:

public class BaseTest {
    static{
        /*** init code here ***/
    }   
}

and every test should inherit from it:

public class SomeTest extends BaseTest {

}
Filip
  • 2,244
  • 2
  • 21
  • 34
1

You can make one BaseTest class with a @BeforeClass method, then have all the other tests inherit from it. This way, when each test object is constructed, @BeforeClass gets executed.

Also avoid executing it just once for all the test suite, since all the test cases should be independent. @BeforeClass should execute only once each test case, not test suite.

Dmitriy
  • 5,525
  • 12
  • 25
  • 38
fedeb
  • 122
  • 1
  • 4
0

If you can tolerate adding spring-test to your project, or you are using it already, then a good approach is to use the technique described here: How to load DBUnit test data once per case with Spring Test

Community
  • 1
  • 1
Kkkev
  • 4,716
  • 5
  • 27
  • 43
0

Not sure if anyone still is using JUnit and trying to fix it without using Spring Runner (aka no spring integration). TestNG has this feature. But here is a JUnit based solution.

Create a RunOnce per thread operation like so. This maintains a list of classes for which the operation has run.

public class RunOnceOperation {
private static final ThreadLocal t = new ThreadLocal();

public void run(Function f) {
    if (t.get() == null) {
        t.set(Arrays.asList(getClass()));
        f.apply(0);
    } else {
        if (!((List) t.get()).contains(getClass())) {
            ((List) t.get()).add(getClass());
            f.apply(0);
        }
    }
  }
}

Back in your unit test

@Before
public beforeTest() {
    operation.run(new Function<Integer, Void>() {
        @Override
        public Void apply(Integer t) {
            checkBeanProperties();
            return null;
        }
    });
}

private void checkBeanProperties() {
   //I only want to check this once per class.
   //Also my bean check needs instance of the class and can't be static.
}


My function interface is like this:

interface Function<I,O> {
 O apply(I i); 
}

When you use this way, you can perform operations once per class using ThreadLocal.

skipy
  • 4,032
  • 2
  • 23
  • 19