118

I marked a method with jUnit's @BeforeClass annotation, and got this exception saying it must be static. What's the rationale? This forces all my init to be on static fields, for no good reason as far as I see.

In .Net (NUnit), this is not the case.

Edit - the fact that a method annotated with @BeforeClass runs only once has nothing to do with it being a static method - one can have a non-static method run only once (as in NUnit).

skaffman
  • 398,947
  • 96
  • 818
  • 769
ripper234
  • 222,824
  • 274
  • 634
  • 905

8 Answers8

128

JUnit always creates one instance of the test class for each @Test method. This is a fundamental design decision to make it easier to write tests without side-effects. Good tests do not have any order-of-run dependencies (see F.I.R.S.T) and creating fresh instances of the test class and its instance variables for each test is crucial in achieving this. Some testing frameworks reuse the same test class instance for all tests, which leads to more possibilities of accidentally creating side-effects between tests.

And because each test method has its own instance, it makes no sense for the @BeforeClass/@AfterClass methods to be instance methods. Otherwise, on which of the test class instances should the methods be called? If it would be possible for the @BeforeClass/@AfterClass methods to reference instance variables, then only one of the @Test methods would have access to those same instance variables - the rest would have the instance variables at their default values - and the @Test method would be randomly selected, because the order of methods in the .class file is unspecified/compiler-dependent (IIRC, Java's reflection API returns the methods in the same order as they are declared in the .class file, although also that behaviour is unspecified - I have written a library for actually sorting them by their line numbers).

So enforcing those methods to be static is the only reasonable solution.

Here is an example:

public class ExampleTest {

    @BeforeClass
    public static void beforeClass() {
        System.out.println("beforeClass");
    }

    @AfterClass
    public static void afterClass() {
        System.out.println("afterClass");
    }

    @Before
    public void before() {
        System.out.println(this + "\tbefore");
    }

    @After
    public void after() {
        System.out.println(this + "\tafter");
    }

    @Test
    public void test1() {
        System.out.println(this + "\ttest1");
    }

    @Test
    public void test2() {
        System.out.println(this + "\ttest2");
    }

    @Test
    public void test3() {
        System.out.println(this + "\ttest3");
    }
}

Which prints:

beforeClass
ExampleTest@3358fd70    before
ExampleTest@3358fd70    test1
ExampleTest@3358fd70    after
ExampleTest@6293068a    before
ExampleTest@6293068a    test2
ExampleTest@6293068a    after
ExampleTest@22928095    before
ExampleTest@22928095    test3
ExampleTest@22928095    after
afterClass

As you can see, each of the tests is executed with its own instance. What JUnit does is basically the same as this:

ExampleTest.beforeClass();

ExampleTest t1 = new ExampleTest();
t1.before();
t1.test1();
t1.after();

ExampleTest t2 = new ExampleTest();
t2.before();
t2.test2();
t2.after();

ExampleTest t3 = new ExampleTest();
t3.before();
t3.test3();
t3.after();

ExampleTest.afterClass();
Esko Luontola
  • 73,184
  • 17
  • 117
  • 128
  • 1
    "Otherwise, on which of the test class instances should the methods be called?" -- On the test instance that the JUnit test running created in order to execute the tests. – Dave Oct 31 '13 at 20:45
  • 1
    In that example it created *three* test instances. There is no *the* test instance. – Esko Luontola Oct 31 '13 at 22:12
  • Yes - I missed that in your example. I was thinking more about when JUnit is invoked from a test running ala Eclipse, or Spring Test, or Maven. In those cases there is one instance of a test class created. – Dave Nov 01 '13 at 01:52
  • No, JUnit always creates lots of instances of the test class, regardless of what us used to launch the tests. It's only if you have a custom Runner for a test class that something different might happen. – Esko Luontola Nov 03 '13 at 00:38
  • While I understand the design decision I think that it's not taking into account the users' business needs. So at the end the internal design decision (which I shouldn't care so much as a user as soon as the lib works well) forces me design choices in my tests that are really bad practices. That's really not agile at all :D – gicappa Jun 19 '17 at 08:17
  • Each Time a person uses static for anything other than a constant, that person does not know how to write proper object oriented code. Each time you use static you kill a test, each time you use a static you destroy the purpose of interfaces and inheritance. Even for UTILITY classes ... you are better of using singletons. Static methods literally disgust me. But I have to leave with them because it is hard to fight against cancer. – 99Sono Aug 16 '17 at 17:55
  • @99Sono So true. That's one more reason for JUnit requiring @BeforeClass/@AfterClass to be static; it highlights that the programmer is writing smelly code which uses global state. – Esko Luontola Aug 17 '17 at 21:29
  • Some tests are fundamentally "not good" according to "FIRST" which makes no sense, since by their very nature they are ordered and depend on other tests being completed. That means previous test results can be cached and you can focus on the currently executing test to pass. These days, JUnit allows this through the `@Order` annotation. Any other opinion on this matter is subjective. – nurettin Jan 31 '19 at 13:57
  • A better way to summarize this answer would be: Since each test uses a newly created object, making `@BeforeClass` and `@AfterClass` non-static will not provide any more functionality than what `@Before` and `@After` already provides. The only functionality that would be needed in this case would be for a static method to initialize and cleanup the class, which `@BefoeClass` and `@AfterClass` provide – Vikhram Apr 12 '21 at 19:09
47

The short answer is this: there is no good reason for it to be static.

In fact, making it static causes all sorts of problems if you use Junit to execute DBUnit based DAO integration tests. The static requirement interferes with dependency injection, application context access, resource handling, logging, and anything that depends on "getClass".

Dave
  • 21,524
  • 28
  • 141
  • 221
  • Do we have any solution for this ? cant we add properties from xml ? – Vinay Veluri Feb 01 '13 at 05:32
  • 4
    I wrote my own test case superclass and use the Spring annotations `@PostConstruct` for set up and `@AfterClass` for tear down and I ignore the static ones from Junit altogether. For DAO tests I then wrote my own `TestCaseDataLoader` class which I invoke from these methods. – Dave Feb 01 '13 at 13:40
  • 9
    That's a terrible answer, clearly there is in fact a reason for it to be static as the accepted answer clearly indicates. You might disagree with the design decision, but that's far from implying that there is "no good reason" for the decision. – Adam Parkin Oct 31 '13 at 16:27
  • 10
    Of course the JUnit authors had a reason, I'm saying its not a *good* reason...thus the source of the OP (and 44 other people) being mystified. It would have been trivial to use instance methods and have the test runners employ a convention to call them. In the end, that's what everyone does in order to workaround this limitation -- either roll your own runner, or roll your own test class. – Dave Nov 01 '13 at 16:57
  • 1
    @HDave, I think that your solution with `@PostConstruct` and `@AfterClass` just behave the same as `@Before` and `@After` . In fact, your methods will be called for each test method and not once for the entire class (as Esko Luontola states in his answer, an instance of class is created for each test method). I can't see the utility of your solution so (unless I miss something) – magnum87 Jun 26 '15 at 13:11
  • 1
    It's been running correctly for 5 years now, so I'm thinking my solution works. – Dave Jun 30 '15 at 04:37
  • 1
    I'm not saying your solution does not work, I'm saying that `@PostConstruct` should behave exactly the same way as `@Before`, ie a method annotated with either annotation will be launched before _each_ test method in your class. – magnum87 Jul 01 '15 at 15:31
14

JUnit documentation seems scarce, but I'll guess: perhaps JUnit creates a new instance of your test class before running each test case, so the only way for your "fixture" state to persist across runs is to have it be static, which can be enforced by making sure your fixtureSetup (@BeforeClass method) is static.

Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
  • 2
    Not only perhaps, but JUnit definitely creates a new instance of a test case. So this is the only reason. – guerda Jun 29 '09 at 07:31
  • This is the only reason they have, but in fact the Junit runner *could* do the job of executing a BeforeTests and AfterTests methods the way testng does. – Dave Jul 13 '10 at 13:06
  • Does TestNG create one instance of the test class and share it with all tests in the class? That makes it more vulnerable to side-effects between tests. – Esko Luontola Jul 15 '10 at 21:44
3

Though this won't answer the original question. It will answers the obvious follow up. How to create a rule that works before and after a class and before and after a test.

To achieve that you can use this pattern:

@ClassRule
public static JPAConnection jpaConnection = JPAConnection.forUITest("my-persistence-unit");

@Rule
public JPAConnection.EntityManager entityManager = jpaConnection.getEntityManager();

On before(Class) the JPAConnection creates the connection once on after(Class) it closes it.

getEntityManger returns an inner class of JPAConnection that implements jpa's EntityManager and can access the connection inside the jpaConnection. On before (test) it begins a transaction on after (test) it rolls it back again.

This isn't thread-safe but can be made to be so.

Selected code of JPAConnection.class

package com.triodos.general.junit;

import com.triodos.log.Logger;
import org.jetbrains.annotations.NotNull;
import org.junit.rules.ExternalResource;

import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.metamodel.Metamodel;
import java.util.HashMap;
import java.util.Map;

import static com.google.common.base.Preconditions.checkState;
import static com.triodos.dbconn.DB2DriverManager.DRIVERNAME_TYPE4;
import static com.triodos.dbconn.UnitTestProperties.getDatabaseConnectionProperties;
import static com.triodos.dbconn.UnitTestProperties.getPassword;
import static com.triodos.dbconn.UnitTestProperties.getUsername;
import static java.lang.String.valueOf;
import static java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;

public final class JPAConnectionExample extends ExternalResource {

  private static final Logger LOG = Logger.getLogger(JPAConnectionExample.class);

  @NotNull
  public static JPAConnectionExample forUITest(String persistenceUnitName) {
    return new JPAConnectionExample(persistenceUnitName)
        .setManualEntityManager();
  }

  private final String persistenceUnitName;
  private EntityManagerFactory entityManagerFactory;
  private javax.persistence.EntityManager jpaEntityManager = null;
  private EntityManager entityManager;

  private JPAConnectionExample(String persistenceUnitName) {
    this.persistenceUnitName = persistenceUnitName;
  }

  @NotNull
  private JPAConnectionExample setEntityManager(EntityManager entityManager) {
    this.entityManager = entityManager;
    return this;
  }

  @NotNull
  private JPAConnectionExample setManualEntityManager() {
    return setEntityManager(new RollBackAfterTestEntityManager());
  }


  @Override
  protected void before() {
    entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName, createEntityManagerProperties());
    jpaEntityManager = entityManagerFactory.createEntityManager();
  }

  @Override
  protected void after() {

    if (jpaEntityManager.getTransaction().isActive()) {
      jpaEntityManager.getTransaction().rollback();
    }

    if(jpaEntityManager.isOpen()) {
      jpaEntityManager.close();
    }
    // Free for garbage collection as an instance
    // of EntityManager may be assigned to a static variable
    jpaEntityManager = null;

    entityManagerFactory.close();
    // Free for garbage collection as an instance
    // of JPAConnection may be assigned to a static variable
    entityManagerFactory = null;
  }

  private Map<String,String> createEntityManagerProperties(){
    Map<String, String> properties = new HashMap<>();
    properties.put("javax.persistence.jdbc.url", getDatabaseConnectionProperties().getURL());
    properties.put("javax.persistence.jtaDataSource", null);
    properties.put("hibernate.connection.isolation", valueOf(TRANSACTION_READ_UNCOMMITTED));
    properties.put("hibernate.connection.username", getUsername());
    properties.put("hibernate.connection.password", getPassword());
    properties.put("hibernate.connection.driver_class", DRIVERNAME_TYPE4);
    properties.put("org.hibernate.readOnly", valueOf(true));

    return properties;
  }

  @NotNull
  public EntityManager getEntityManager(){
    checkState(entityManager != null);
    return entityManager;
  }


  private final class RollBackAfterTestEntityManager extends EntityManager {

    @Override
    protected void before() throws Throwable {
      super.before();
      jpaEntityManager.getTransaction().begin();
    }

    @Override
    protected void after() {
      super.after();

      if (jpaEntityManager.getTransaction().isActive()) {
        jpaEntityManager.getTransaction().rollback();
      }
    }
  }

  public abstract class EntityManager extends ExternalResource implements javax.persistence.EntityManager {

    @Override
    protected void before() throws Throwable {
      checkState(jpaEntityManager != null, "JPAConnection was not initialized. Is it a @ClassRule? Did the test runner invoke the rule?");

      // Safety-close, if failed to close in setup
      if (jpaEntityManager.getTransaction().isActive()) {
        jpaEntityManager.getTransaction().rollback();
        LOG.error("EntityManager encountered an open transaction at the start of a test. Transaction has been closed but should have been closed in the setup method");
      }
    }

    @Override
    protected void after() {
      checkState(jpaEntityManager != null, "JPAConnection was not initialized. Is it a @ClassRule? Did the test runner invoke the rule?");
    }

    @Override
    public final void persist(Object entity) {
      jpaEntityManager.persist(entity);
    }

    @Override
    public final <T> T merge(T entity) {
      return jpaEntityManager.merge(entity);
    }

    @Override
    public final void remove(Object entity) {
      jpaEntityManager.remove(entity);
    }

    @Override
    public final <T> T find(Class<T> entityClass, Object primaryKey) {
      return jpaEntityManager.find(entityClass, primaryKey);
    }

    @Override
    public final <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties) {
      return jpaEntityManager.find(entityClass, primaryKey, properties);
    }

    @Override
    public final <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode) {
      return jpaEntityManager.find(entityClass, primaryKey, lockMode);
    }

    @Override
    public final <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties) {
      return jpaEntityManager.find(entityClass, primaryKey, lockMode, properties);
    }

    @Override
    public final <T> T getReference(Class<T> entityClass, Object primaryKey) {
      return jpaEntityManager.getReference(entityClass, primaryKey);
    }

    @Override
    public final void flush() {
      jpaEntityManager.flush();
    }

    @Override
    public final void setFlushMode(FlushModeType flushMode) {
      jpaEntityManager.setFlushMode(flushMode);
    }

    @Override
    public final FlushModeType getFlushMode() {
      return jpaEntityManager.getFlushMode();
    }

    @Override
    public final void lock(Object entity, LockModeType lockMode) {
      jpaEntityManager.lock(entity, lockMode);
    }

    @Override
    public final void lock(Object entity, LockModeType lockMode, Map<String, Object> properties) {
      jpaEntityManager.lock(entity, lockMode, properties);
    }

    @Override
    public final void refresh(Object entity) {
      jpaEntityManager.refresh(entity);
    }

    @Override
    public final void refresh(Object entity, Map<String, Object> properties) {
      jpaEntityManager.refresh(entity, properties);
    }

    @Override
    public final void refresh(Object entity, LockModeType lockMode) {
      jpaEntityManager.refresh(entity, lockMode);
    }

    @Override
    public final void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties) {
      jpaEntityManager.refresh(entity, lockMode, properties);
    }

    @Override
    public final void clear() {
      jpaEntityManager.clear();
    }

    @Override
    public final void detach(Object entity) {
      jpaEntityManager.detach(entity);
    }

    @Override
    public final boolean contains(Object entity) {
      return jpaEntityManager.contains(entity);
    }

    @Override
    public final LockModeType getLockMode(Object entity) {
      return jpaEntityManager.getLockMode(entity);
    }

    @Override
    public final void setProperty(String propertyName, Object value) {
      jpaEntityManager.setProperty(propertyName, value);
    }

    @Override
    public final Map<String, Object> getProperties() {
      return jpaEntityManager.getProperties();
    }

    @Override
    public final Query createQuery(String qlString) {
      return jpaEntityManager.createQuery(qlString);
    }

    @Override
    public final <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery) {
      return jpaEntityManager.createQuery(criteriaQuery);
    }

    @Override
    public final <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass) {
      return jpaEntityManager.createQuery(qlString, resultClass);
    }

    @Override
    public final Query createNamedQuery(String name) {
      return jpaEntityManager.createNamedQuery(name);
    }

    @Override
    public final <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass) {
      return jpaEntityManager.createNamedQuery(name, resultClass);
    }

    @Override
    public final Query createNativeQuery(String sqlString) {
      return jpaEntityManager.createNativeQuery(sqlString);
    }

    @Override
    public final Query createNativeQuery(String sqlString, Class resultClass) {
      return jpaEntityManager.createNativeQuery(sqlString, resultClass);
    }

    @Override
    public final Query createNativeQuery(String sqlString, String resultSetMapping) {
      return jpaEntityManager.createNativeQuery(sqlString, resultSetMapping);
    }

    @Override
    public final void joinTransaction() {
      jpaEntityManager.joinTransaction();
    }

    @Override
    public final <T> T unwrap(Class<T> cls) {
      return jpaEntityManager.unwrap(cls);
    }

    @Override
    public final Object getDelegate() {
      return jpaEntityManager.getDelegate();
    }

    @Override
    public final void close() {
      jpaEntityManager.close();
    }

    @Override
    public final boolean isOpen() {
      return jpaEntityManager.isOpen();
    }

    @Override
    public final EntityTransaction getTransaction() {
      return jpaEntityManager.getTransaction();
    }

    @Override
    public final EntityManagerFactory getEntityManagerFactory() {
      return jpaEntityManager.getEntityManagerFactory();
    }

    @Override
    public final CriteriaBuilder getCriteriaBuilder() {
      return jpaEntityManager.getCriteriaBuilder();
    }

    @Override
    public final Metamodel getMetamodel() {
      return jpaEntityManager.getMetamodel();
    }
  }
}
M.P. Korstanje
  • 10,426
  • 3
  • 36
  • 58
3

It seems that JUnit creates a new instance of the test class for each test method. Try this code out

public class TestJunit
{

    int count = 0;

    @Test
    public void testInc1(){
        System.out.println(count++);
    }

    @Test
    public void testInc2(){
        System.out.println(count++);
    }

    @Test
    public void testInc3(){
        System.out.println(count++);
    }
}

The output is 0 0 0

This means that if the @BeforeClass method is not static then it will have to be executed before each test method and there would be no way to differentiate between the semantics of @Before and @BeforeClass

Walery Strauch
  • 6,792
  • 8
  • 50
  • 57
randomuser
  • 1,858
  • 2
  • 17
  • 21
  • It doesn't just _seem_ that way, it _is_ that way. The question has been asked for many years, here is the answer: http://martinfowler.com/bliki/JunitNewInstance.html – Paul Jan 13 '12 at 06:37
1

there are two types of annotations:

  • @BeforeClass (@AfterClass) called once per test class
  • @Before (and @After) called before each test

so @BeforeClass must be declared static because it is called once. You should also consider that being static is the only way to ensure proper "state" propagation between tests (JUnit model imposes one test instance per @Test) and, since in Java only static methods can access static data... @BeforeClass and @AfterClass can be applied only to static methods.

This example test should clarify @BeforeClass vs @Before usage:

public class OrderTest {

    @BeforeClass
    public static void beforeClass() {
        System.out.println("before class");
    }

    @AfterClass
    public static void afterClass() {
        System.out.println("after class");
    }

    @Before
    public void before() {
        System.out.println("before");
    }

    @After
    public void after() {
        System.out.println("after");
    }    

    @Test
    public void test1() {
        System.out.println("test 1");
    }

    @Test
    public void test2() {
        System.out.println("test 2");
    }
}

output:

------------- Standard Output ---------------
before class
before
test 1
after
before
test 2
after
after class
------------- ---------------- ---------------
dfa
  • 114,442
  • 31
  • 189
  • 228
  • 20
    I find your answer irrelevant. I know the semantics of BeforeClass and Before. This doesn't explain why it has to be static... – ripper234 Jun 27 '09 at 12:21
  • 1
    "This forces all my init to be on static members, for no good reason as far as I see." My answer should show you that your init can be also *non-static* using @Before, instead of @BeforeClass – dfa Jun 27 '09 at 12:54
  • 3
    I'd like to do some of the init one time only, at the start of the class, but on non-static variables. – ripper234 Jun 27 '09 at 16:47
  • you cannot with JUnit, sorry. You must use a static variable, no way. – dfa Jun 27 '09 at 19:29
  • 1
    If the initialization is expensive, you could just keep a state variable to record whether you've done the init, and (check it and optionally) perform the init in a @Before method... – Blair Conrad Jun 29 '09 at 10:43
  • static is just a *simpler* solution – dfa Jun 29 '09 at 10:53
  • @Blair a state variable solution wouldn't work if you then wanted to tear down in @After unless you kept a count of the number of tests .. I smell a hack – Blundell May 31 '11 at 10:25
1

As per JUnit 5, it seems the philosophy on strictly creating a new instance per test method has been somewhat loosened. They have added an annotation that will instantiate a test class only once. This annotation therefore also allows methods annotated with @BeforeAll/@AfterAll (the replacements to @BeforeClass/@AfterClass) to be non-static. So, a test class like this:

@TestInstance(Lifecycle.PER_CLASS)
class TestClass() {
    Object object;

    @BeforeAll
    void beforeAll() {
        object = new Object();
    }

    @Test
    void testOne() {
        System.out.println(object);
    }

    @Test
    void testTwo() {
        System.out.println(object);
    }
}

would print:

java.lang.Object@799d4f69
java.lang.Object@799d4f69

So, you can actually instantiate objects once per test class. Of course, this does make it your own responsibility to avoid mutating objects that are instantiated this way.

EJJ
  • 156
  • 1
  • 7
  • Very nice pointing this out. Using `Lifecycle.PER_CLASS` makes much sense for a lot of test classes and it removes all the static hassle – Nico Van Belle Oct 23 '20 at 07:27
-12

To resolve this issue just change the method

public void setUpBeforeClass 

to

public static void setUpBeforeClass()

and all that are defined in this method to static.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
sri
  • 1