Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle ordering of @Rule's when they are dependent on each other

Tags:

java

junit

junit4

I use embedded servers that run inside Junit test cases. Sometimes these servers require a working directory (for example the Apache Directory server).

The new @Rule in Junit 4.7 can handle these cases. The TemporaryFolder-Rule can create a temporary directory. A custom ExternalResource-Rule can be created for server. But how do I handle if I want to pass the result from one rule into another:

import static org.junit.Assert.assertEquals;
import java.io.*;
import org.junit.*;
import org.junit.rules.*;

public class FolderRuleOrderingTest {

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    @Rule
    public MyNumberServer server = new MyNumberServer(folder);

    @Test
    public void testMyNumberServer() throws IOException {
        server.storeNumber(10);
        assertEquals(10, server.getNumber());
    }

    /** Simple server that can store one number */
    private static class MyNumberServer extends ExternalResource {

        private TemporaryFolder folder;

        /** The actual datafile where the number are stored */
        private File dataFile;

        public MyNumberServer(TemporaryFolder folder) {
            this.folder = folder;
        }

        @Override
        protected void before() throws Throwable {
            if (folder.getRoot() == null) {
                throw new RuntimeException("TemporaryFolder not properly initialized");
            }

            //All server data are stored to a working folder
            File workingFolder = folder.newFolder("my-work-folder");
            dataFile = new File(workingFolder, "datafile");
        }

        public void storeNumber(int number) throws IOException {
            dataFile.createNewFile();
            DataOutputStream out = new DataOutputStream(new FileOutputStream(dataFile));
            out.writeInt(number);
        }

        public int getNumber() throws IOException {
            DataInputStream in = new DataInputStream(new FileInputStream(dataFile));
            return in.readInt();
        }
    }
}

In this code the folder is sent as a parameter into the server so that the server can create a working directory to store data. However this does not work because Junit processes the rules in reverse order as they are defined in the file. The TemporaryFolder Rule will not be executed before the server Rule. Thus the root-folder in TempraryFolder will be null, resulting that any files are created relative to the current working directory.

If I reverse the order of the attributes in my class I get a compile error because I cannot reference a variable before it is defined.

I'm using Junit 4.8.1 (because the ordering of rules was fixed a bit from the 4.7 release)

like image 242
Lennart Schedin Avatar asked Apr 28 '10 14:04

Lennart Schedin


4 Answers

EDIT: With the recently released Junit 4.10, you can use RuleChain to chain rules correctly (see at the end).

You could introduce another private field without the @Rule annotation, then you can reorder your code as you wish:

public class FolderRuleOrderingTest {

    private TemporaryFolder privateFolder = new TemporaryFolder();

    @Rule
    public MyNumberServer server = new MyNumberServer(privateFolder);

    @Rule
    public TemporaryFolder folder = privateFolder;

    @Test
    public void testMyNumberServer() throws IOException {
        server.storeNumber(10);
        assertEquals(10, server.getNumber());
    }
    ...
}

The cleanest solution is to have a compound rule, but the above should work.

EDIT: With the recently released Junit 4.10, you can use RuleChain to chain rules correctly:

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

   @Test
   public void example() {
           assertTrue(true);
   }
}

writes the log

starting outer rule
starting middle rule
starting inner rule
finished inner rule
finished middle rule
finished outer rule
like image 111
Matthew Farwell Avatar answered Nov 19 '22 18:11

Matthew Farwell


To make the rules dependent, your have to initialize them first and create the dependency relationships using contructors or (depending on you rule) fluent builders. The dependency relations have to be defined in the field-initialization and could not be created in @Before methods as those are executed after rule application. To force the correct ordering of rule execution, you have to define the rule chain.

public class FolderRuleOrderingTest {

  private TemporaryFolder folder = new TemporaryFolder();
  //assume, we have a rule that creates a testfile in a temporary folder
  //we create a dependency relationship between file and folder,
  //so that file depends on folder
  private TemporaryFile file = new TemporaryFile(folder, "testfile.txt");

  //the rule chain ensures, the temporary folder is created before and removed 
  //after the testfile has been created and deleted (or whatever)
  @Rule
  public RuleChain chain= RuleChain.outerRule(folder).around(file));


  @Test
  public void testFileExist() throws IOException {
    assertTrue(file.getFile().exist());
  }
  ...
}
like image 22
Gerald Mücke Avatar answered Nov 19 '22 17:11

Gerald Mücke


If you will not find normal solution, you can always create compound rule (and the only one having @Rule annotation) that contains all others and executes them in order.

like image 3
Ha. Avatar answered Nov 19 '22 16:11

Ha.


You can also use the attribute order since 4.13:

You can use order() if you want to have control over the order in which the Rules are applied.

   public class ThreeRules {
       @Rule(order = 0)
       public LoggingRule outer = new LoggingRule("outer rule");
  
       @Rule(order = 1)
       public LoggingRule middle = new LoggingRule("middle rule");
  
       @Rule(order = 2)
       public LoggingRule inner = new LoggingRule("inner rule");
  
       // ...
   }
like image 2
Evin1_ Avatar answered Nov 19 '22 18:11

Evin1_