JUnit Rules and Spock

I recently had to implement a file upload for a Grails application. In this application we have quite a bunch of JUnit tests but we do want to utilize Spock for all the newly added tests.

As it is in the nature of file uploads, the Spock specification needs to create quite a few temporary files and folders.

One option I’ve seen quite a few times is to either use File#createTemp or use JDK 7’s Files#createTempDirectory/Files#createTempFile methods. Both have the disadvantage of the intial setup and cleanup, which results in helper code that is added to the test and distracts from the real test code.

JUnit Rules

JUnit provides a way to intercept the test suite and test method execution by providing the concept of rules. A rule implementation can intercept test method execution and alter the behaviour of these tests, or add cleanup work as it was done in @Before, @After, @BeforeClass and @AfterClass or Spock’s setup, cleanup, setupSpec and cleanupSpec methods. For a particular test case, an instance of the rule implementation must be available via a public instance field and it must be annotated with @Rule.

For example, the TestName rule implementation can be used to have access to the test case name in a test method at runtime.

public class NameRuleTest {
  @Rule
  public TestName name = new TestName();

  @Test
  public void testA() {
    assertEquals("testA", name.getMethodName());
  }

  @Test
  public void testB() {
    assertEquals("testB", name.getMethodName());
  }
}

Spock and JUnit Rules

As it turns out, Spock has support for applying JUnit rules in specifications. This be done by providing a Groovy property with the type of the rule implementation, annotated by @Rule. This was really good news for my file upload tests as this allowed me to use one of my favorite JUnit rules: the org.junit.rules.TemporaryFolder rule.

As its name implies, the TemporaryFolder rule gives a convenient way to create temporary folders and files in test methods. The rule concept is used to intercept before and after each test method execution to do all the setup work.

This makes testing my AttachmentService very slick:

@TestFor(AttachmentService)
@Mock([Attachment])
class AttachmentServiceSpec extends Specification {

    @Rule
    TemporaryFolder temporaryFolder // see Peter's comment below :-) = new TemporaryFolder()

    def "load the persistent file for a given attachment"() {
        setup:
          def tempFile = temporaryFolder.newFile('test.txt')
          def attachment = new Attachment(
                               uploadId: '123', 
                               originalFilename: 'test.txt', 
                               location: tempFile.toURL()).save()

        when: "an attachment with a URL reference is loaded"
          def file = service.loadFile(attachment)

        then: "the underyling File must be returned"
          file == tempFile
    }
}

As you can see in the code above, the TemporaryFolder can be used to create a new file with the newFile method. If we wanted to create a new folder, there is also a newFolder method available. We do not have to specify any temporary folder or do any cleanup work, this is all done by the rule implementation itself.

There is a good overview for the base rules provided in JUnit at Github.

Conclusion

Spock comes with support for JUnit rules. A rule can intercept test method execution, do setup or cleanup work and might even change the test results. The TemporaryFolder rule is a useful rule that allows to create temporary files and folders in test cases while keeping track of these files and cleaning them up after the test execution.