Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to order @Before methods

I have a trait that adds a couple of tests and before blocks. The @Before blocks of the concrete instance are run before the ones in the trait. Oops, this means I can't truncate database tables then insert fixtures:

trait DatabaseTest {
  @Before
  def truncate() {
    // "TRUNCATE %s".format(tableName)
  }

  def tableName
}

class PersonasTest extends DatabaseTest {
  @Before
  def addPersona() {
    // "INSERT INTO %s VALUES (...)".format(tableName)
  }


  @Test
  def testRejectsInsertWhenAlreadyInTable() {
    // "INSERT INTO %s VALUES (...)".format(tableName)
  }

  def tableName = "personas"
}

testRejectsInsertWhenAlreadyInTable will always succeed because the execution order will be:

  • addPersona
  • truncate
  • testRejectsInsertWhenAlreadyInTable

What would be the proper way of ordering the @Before blocks without imposing too many constraints on the subclasses? I could always declare truncate in the trait, then have an @Before method in the subclass, but then I have to remember to make all my subclasses call that truncate method.

Using JUnit 4.10 on Scala 2.9.0.1.

like image 321
François Beausoleil Avatar asked Apr 28 '26 19:04

François Beausoleil


1 Answers

The correct way of doing this is to use @Rule, extend @ExternalResource for before and after type behaviour (in Java syntax):

@Rule
public ExternalResource resource= new ExternalResource() {
        @Override
        protected void before() throws Throwable {
                myServer.connect();
        };

        @Override
        protected void after() {
                myServer.disconnect();
        };
};

You can chain and order multiple @Rules together in order by using the @RuleChain (introduced in 4.10), again in java syntax:

@Rule
public TestRule chain= RuleChain
                       .outerRule(new LoggingRule("outer rule")
                       .around(new LoggingRule("middle rule")
                       .around(new LoggingRule("inner rule");

There is a caveat. You can't specify a public field in scala (public field's are wrapped with accessor methods, and the fields themselves become private). JUnit checks that the @Rule applies to a public field. The fix to change the JUnit code so that you can apply @Rule to methods as well as fields.

This has been fixed (by me), and has been merged into master, but unfortunately, it's not yet released: it will be part of 4.11. So you have two options: use the 4.11-SNAPSHOT, or download the 4.10 release and apply the patch for @Rule.

The scala code could look something like:

trait DatabaseTest {
    def truncate(): TestRule = {
        new ExternalResource() {
            override def before() = {
                // "TRUNCATE %s".format(tableName)
            }
        }
    }

    def extra(): TestRule = {
        // return a no-op rule
    }

    @Rule def testRule() = new RuleChain(truncate(), extra())
    def tableName
}

class PersonasTest extends DatabaseTest {
  def extra(): TestRule {
        new ExternalResource() {
            override def before() = {
                // "INSERT INTO %s VALUES (...)".format(tableName)
            }
        }
  }

  @Test
  def testRejectsInsertWhenAlreadyInTable() {
    // "INSERT INTO %s VALUES (...)".format(tableName)
  }

  def tableName = "personas"
}
like image 78
Matthew Farwell Avatar answered Apr 30 '26 10:04

Matthew Farwell



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!