Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@BeforeClass requiring the method to be static is nonsense #122

Closed
subes opened this issue Jul 15, 2010 · 19 comments
Closed

@BeforeClass requiring the method to be static is nonsense #122

subes opened this issue Jul 15, 2010 · 19 comments

Comments

@subes
Copy link

subes commented Jul 15, 2010

Hi,

JUnit is the only testing Framework which requires @BeforeClass Annotated methods to be static. This makes @BeforeClass mostly useless für Enterprise Applications, because often you have to do a setUpOnce after the dependency injection has been done (e.g. Spring or some other Framework).

As a workaround @before with a boolean field and an initialized check is often used which is against the design of JUnits @before, but is required because @BeforeClass is mutilated by design...

Also see:
http://stackoverflow.com/questions/1052577/why-must-junits-fixturesetup-be-static

Best Regards
subes

@subes
Copy link
Author

subes commented Jul 15, 2010

other workarounds are:

  • afterPropertiesSet (InitializingBean interface) in Spring (such a mechanism may not be available with other frameworks, e.g. the proprietary one at my workplace)
  • or setting up the initializing method with @test, which also destroys the use case of running test-methods isolated from each other

@subes
Copy link
Author

subes commented Jul 27, 2010

i found a nice workaround which works for every environment. in java there is something like class initializers which are written like static initializers, just without the static. :D

See: http://stackoverflow.com/questions/1052577/why-must-junits-fixturesetup-be-static

@subes
Copy link
Author

subes commented Aug 2, 2010

the class initializer aint a workaround because it acts just like a @before, because for every test method, a new test object gets initialized...

why is that so?

@dsaff
Copy link
Member

dsaff commented Mar 4, 2011

@BeforeClass must be static because, by design, there is no instance of the class to invoke the @BeforeClass method on. There is an instance of the class at the time of every method invocation. This is a deeply assumed part of the design of several JUnit classes.

That said, I'd like to help solve your problem. Can you rephrase the issue as a positive request? "I'd like a way to..."

@subes
Copy link
Author

subes commented Mar 9, 2011

I'd like a way to have a method annotated, which is only run once on the first test instance before the first test method is run. (Something between @BeforeClass and @before)
Analogous, I'd like a way to have a method annotated, which is only run once on the last test instance after the last test method is run. (Something between @afterclass and @after)

I've attached my abstract test class which allows me to do that by remembering the last test instance statically and using the @before and @afterclass annotations to invoke my setUpOnce and tearDownOnce methods which can be overriden in tests like it was done in JUnit3 times (going against the usage of JUnit 4 annotations).

@subes
Copy link
Author

subes commented Mar 9, 2011

package de.invesdwin.gemeinsam.test;

import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Resource;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;

import com.google.common.base.Preconditions;

import de.invesdwin.gemeinsam.beans.context.PreMergedContext;
import de.invesdwin.gemeinsam.log.Log;
import de.invesdwin.gemeinsam.log.LogFactory;
import de.invesdwin.gemeinsam.test.lifecycle.ITestLifecycleHook;
import de.invesdwin.gemeinsam.test.lifecycle.internal.ITestLifecycle;
import de.invesdwin.gemeinsam.zeit.Zeitpunkt;

/**
 * TransactionalTestExecutionListener kollidiert mit CTW Transaktionen.
 * 
 * @author subes
 * 
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { TestContextLoader.CTX_DUMMY }, loader = TestContextLoader.class)
@ThreadSafe
@TestExecutionListeners({ DirtiesContextTestExecutionListener.class, DependencyInjectionTestExecutionListener.class })
public abstract class ATest implements ITestLifecycle {

    private static AtomicInteger testId = new AtomicInteger();
    @GuardedBy("this.class")
    private static ATest letzteTestInstanz;

    protected final Log log = LogFactory.getLog(this);
    protected TestContext ctx;
    private Zeitpunkt messung = new Zeitpunkt();

    static {
        Preconditions.checkState(PreMergedContext.INSTANCE != null);
    }

    @Resource
    private ITestLifecycleHook[] hooks;

    public ATest() {
        TestContextLoader.setAktuellerTest(this);
    }

    public void setUpContext(TestContext ctx) {
        this.ctx = ctx;
    }

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        synchronized (ATest.class) {
            if (letzteTestInstanz == null) {
                letzteTestInstanz = this;
                setUpOnce();
            }
        }
        for (ITestLifecycleHook hook : hooks) {
            hook.setUp(ctx);
        }
        messung = new Zeitpunkt();
    }

    public void setUpOnce() {
        for (ITestLifecycleHook hook : hooks) {
            hook.setUpOnce(ctx);
        }
    }

    @After
    public void tearDown() {
        log.info(testId.incrementAndGet() + ". Test beendet nach: " + messung);
        for (ITestLifecycleHook hook : hooks) {
            hook.tearDown(ctx);
        }
    }

    public void tearDownOnce() {
        for (ITestLifecycleHook hook : hooks) {
            hook.tearDownOnce(ctx);
        }
    }

    @AfterClass
    public static synchronized void tearDownOnceStatic() {
        if (letzteTestInstanz != null) {
            letzteTestInstanz.tearDownOnce();
            TestContextLoader.setAktuellerTest(null);
            letzteTestInstanz = null;
        }
    }

}

I am doing various things with the ITestLifecycleHooks, like resetting the database, reconfiguring the spring context beans or overriding mocks via Mockito in the setUpOnce() and tearDownOnce() methods (all requiring Spring-Dependecy-Injection to occur before being run).

A neat solution in JUnit would surely be valued by many people who have the same requirements. Thanks for your help. :)

@dsaff
Copy link
Member

dsaff commented Mar 9, 2011

Got it. This will be much easier if we can get something like https://github.com/KentBeck/junit/issues/199 done, allowing a single rule to work at both the class-wide level and each-method level.

@phoenix
Copy link

phoenix commented Apr 3, 2013

This issue was closed 2 years ago,but it's still not be resolved,can we reopen it?

@dsaff dsaff reopened this Apr 3, 2013
@dsaff
Copy link
Member

dsaff commented Apr 3, 2013

I can reopen for more discussion. I think there's a base problem that we should solve, that I don't understand yet.

@doronl
Copy link

doronl commented Dec 31, 2013

Hi,
Any news regarding this? it is quite annoying.

Thanks!

@kcooney
Copy link
Member

kcooney commented Jan 29, 2014

I'm tempted to close this. The top-voted answer at http://stackoverflow.com/questions/1052577/why-must-junits-fixturesetup-be-static explains in detail why we can't allow @BeforeClass in non-static methods. In short, JUnit always creates a unique instance of your test class for each test method, so there is no single instance to call for class-level initialization.

It does sound like there is a desire to have a single rule that can work at both the class-wide level and each-method level. If that would solve the core problem, then we should track that in a bug that states that as the goal.

@kcooney kcooney closed this as completed Jul 13, 2014
@dougschroeder
Copy link

It's so frustrating to write an abstract unit test class to test DB calls with JUnit b/c of this. If my abstract class only has one implementer this works fine, but as soon as we have 2 or more and they get run in parallel, we start having state issues because of the static state variables we have to have (either that or we have to create new databases for each test which is kind of overkill and slows test execution time down). I miss JUnit 3 and TestNG -- these are a better fit for these kinds of tests since it appears JUnit 4 will never be fixed to support this kind of test (abstract test classes that need to initialize state once for all tests in the implementing sub-class).

@kcooney
Copy link
Member

kcooney commented Mar 21, 2015

@dougschroeder How did you do per-test-class initialization in JUnit3?

Instead of having a common base class for database tests, have you considered using@ClassRule?

@dougschroeder
Copy link

Hi @kcooney, thank you for the quick response! Actually, it looks like our JUnit 3 tests were not doing one-time initialization in non-static methods either. I was able to get a solution working in JUnit 4 that allows me to do class-level setup and teardown in non-static methods by extending BlockJUnit4ClassRunner and having my base test class extend an interface that allows it to setup and tear down via an instance method. I used this blog article as a guide:

http://saltnlight5.blogspot.com/2012/09/enhancing-spring-test-framework-with.html

I didn't know about @ClassRule -- will have to look at that some more. Thanks.

@kcooney
Copy link
Member

kcooney commented Mar 22, 2015

@dougschroeder while the solution on the blog post would allow a one-time setup to.happen in a non-static method, unless I am missing something, the one-time setup would have to store state in a static field if you want the data available in more that one test.

For the use case you give, you use need 1) a class extending ExternalResource that sets up your database, and 2) in classes needing a database, a static field of that type annotated with @ClassRule. No base test case needed.

@dougschroeder
Copy link

@kcooney can I use that without having to group my implementations into a Suite? I'd like to define my base test class in a common jar and not have to know about the tests that extend my base class.

To work around having to store my state in a static field, I had the beforeClassSetup method in my interface return an object and added another method to my interface (beforeTestSetup) to pass that object to tests:

public interface InstanceTestClassListener {
    Properties beforeClassSetup();
    void beforeTestSetup(Properties state);
    void afterClassSetup(Properties state);
}

Not the most elegant solution, so I'd rather use ExternalResource and @ClassRule if I can use it outside of a Suite (it sounds like I can since it says BlockJUnit4ClassRunner will support @ClassRule).

@kcooney
Copy link
Member

kcooney commented Mar 22, 2015

@dougschroeder you don't need to define a suite.

@kirenpillay
Copy link

Using the Rules annotation is very easy to use and solved the problem for me.

http://stackoverflow.com/a/24267763/3795456

@ekeeton
Copy link

ekeeton commented Jun 23, 2016

Just use testNg - does not require methods annotated @BeforeClass to be static - problem solved the way it should be

@junit-team junit-team locked and limited conversation to collaborators Jun 24, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants