0
votes

I am using JUnit 4 to test a backend system with an in-memory database. I am using @BeforeClass @Before @After and @AfterClass.

It is working great so far on the class level.

@BeforeClass contains the database setup which is slow but only needs to be done once per test session.

@Before just wipes the slate clean for the next test to run. It's pretty fast.

My tests looks something like this :

class CompanyTest  {

    @BeforeClass
    public static void beforeAll() throws Exception {
        BeforeAll.run(); //Setup In Memory Database!!! Very time intensive!!!
    }
    @Before
    public void beforeTest() throws Exception {
        BeforeTest.run(); //Setup data in database. Relatively quick
    }


    @Test
    public void testCreateCompany() throws Exception {
        ///...
    }
    @Test
    public void testDeleteCompany() throws Exception {
        ///...
    }
    @Test
    public void testAdminCompany() throws Exception {
        ///...
    }


    @After
    public void afterTest() {
        AfterTest.run(); //clear data
    }
    @AfterClass
    public static void afterAll() throws Exception {
        AfterAll.run(); //tear down database
    }
}

It is working great so far on the class level.

I can right click (in Eclipse) on an individual test and it will run @BeforeClass and then @Before.

I can also click (in Eclipse) on the class itself, and it will run @BeforeClass only once and then @Before before every test.

....but how is this principle extended to the Suite level?

I want to run @BeforeClass before all my classes in my Suite. If I write my Suite like this :

@Suite.SuiteClasses({ CompanyTest.class, CustomerTest.class, SomeOtherTest.class, })

public class AllTests {

    @BeforeClass
    public static void beforeAll() throws Exception {
        BeforeAll.run();
    }

    @AfterClass
    public static void afterAll() throws Exception {
        AfterAll.run();
    }
}

... I need to remove @BeforeClass from all my test classes. This is annoying because I have a lot of test classes, and i don't want to delete my @BeforeClass because I want to test them indicidually.

What I am basically saying is :

Is there an easy way (click of a mouse in IDE) to test JUnit tests at the (a) method level, (b) class level, and (c) suite level, while maintaining a session level setUp and tearDown process?

2
Instead of removing @BeforeClass from all your test classes, you could add a static counter "nestingLevel", increment it every BeforeAll.run(), decrement it every AfterAll.run(), and do the actual setup/teardown only when the nestingLevel is 1. - Yoav Gur
A suggestion I gave in a similar question would be to use a JUnit (class) rule and check within the rule if it was already initialized before and skip the initialization in that case. This should allow you to reuse the Rule for both suites and concrete test-classes. - Roman Vottner

2 Answers

1
votes

What you require, is that the global state is managed through different entry points. In your BeforeAll or AfterAll class you could keep a static reference, i.e. an AtomicBoolean to track whether you already executed it or not.

The problem now - with two separate classes for beforeAll and afterAll - which class should be responsible for that flag.

Therefore I suggest, you define a Rule that contains all the logic for setup and teardown (basically all the code of BeforeAll and AfterAll) and that manages the global state for tracking the initialization.

Basically something like

class MyRule implements TestRule {
    static final AtomicBoolean INITIALIZED = new AtomicBoolean();

    @Override
    public Statement apply(final Statement base, final Description description) {

        return new Statement() {

            @Override
            public void evaluate() throws Throwable {
                boolean needsTearDown = false;
                try {
                    if (INITIALIZED.compareAndSet(false, true)) {
                        BeforeAll.run();
                        needsTearDown = true;
                    }
                    base.evaluate();
                } finally {
                    if (needsTearDown) {
                        AfterAll.run();
                        INITIALIZED.set(false);
                    }
                }
            }
        };
    }
}

And in your Test and Suite just add

class YourTestOrSuite {

  @ClassRule
  public static MyRule rule = new MyRule();
}
1
votes

So I have a similar issue (DB initialization, connection pool, cached data, etc) that needs to be done once before running any and all tests. This is normally done when application server starts. For tests that require this I added a base class:

public class BaseInitializedTest {
    private boolean isInitialized;

    @BeforeClass
    public static void init() {
        doOnetimeInit();

        // Do other per unique class initialization here
    }

    private static synchronized void doOnetimeInit() {
        if (!isInitialized) {
            // Do you one time init here

            isInitialized = true;
        }
    }

    @AfterClass
    public static void deinit() {
        // Cleanup
    }
}

And then my tests that require initialization:

public class SomethingTest extends BaseInitializedTest {
    @Test
    public testSomething() {
        // Your test here
    }
}

Not all tests need the stack initialized, but those that do will inheit from this base class and it will handle init correctly. Hope that helps.