Shadov
Shadov

Reputation: 5592

How to set up & clean up a resource in Spock for a test suite

I have this code in my JUnit suite:

@RunWith(Suite.class)
@Suite.SuiteClasses({ MyJavaTest.class, MyAnotherJavaTest.class })
public class MyIntegrationSuite {
    @BeforeClass
    public static void beforeTests() throws Exception {
        Container.start();
    }

    @AfterClass
    public static void afterTests() throws Exception {
        Container.stop();
    }
}

I want to rewrite it to Spock, but I can't find any way to do a global setup, only setupSpec() and setup() which is not enough, since I have multiple specifications and I want to start a Container only once.

I tried leaving the suite as it is and passing Spock specifications to it, but the spock tests are skipped entirely (when I add extends Specifications). It's probably, because Specification has @RunWith(Sputnik) and it doesn't play with @RunWith(Suite), but I'm not sure how to get around that.

Is there any way to do a global setup with Spock or execute Spock specifications from JUnit suite?

My pom.xml (stubs are there, because I'm mixing java and groovy code):

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <version>${buildhelper.plugin.version}</version>
  <executions>
    <execution>
      <id>add-groovy-test-source</id>
      <phase>test</phase>
      <goals>
        <goal>add-test-source</goal>
      </goals>
      <configuration>
        <sources>
          <source>${basedir}/src/test/groovy</source>
        </sources>
      </configuration>
    </execution>
  </executions>
</plugin>
<plugin>
  <groupId>org.codehaus.gmavenplus</groupId>
  <artifactId>gmavenplus-plugin</artifactId>
  <version>${gmavenplus.plugin.version}</version>
  <executions>
    <execution>
      <goals>
        <goal>generateTestStubs</goal>
        <goal>compileTests</goal>
        <goal>removeTestStubs</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Upvotes: 3

Views: 4511

Answers (3)

slnowak
slnowak

Reputation: 1919

Actually, it is possible in spock, assuming your code could be static.

To perform one-time initialization (spinup a container, start a test zookeeper cluster or whatever), just create a static singleton holder like the following:

class Postgres {

    private static PostgresContainer container

    static void init() {
        if (container != null)
            return

        container = new PostgresContainer()
        container.start()
    }

    static void destroy() {
        if (container == null)
            return

        container.stop()
        container = null
    }
}

Then you need an abstract class for your integration tests, something like:

class IntegrationTest extends Specification {

    def setup() {
        init()
    }

    static void init () {
        Postgres.init()
    }

    def cleanup() {
        // do whatever you need
    }

    static void destroy() {
        Postgres.destroy()
    }
}

Now, cleanup is a little bit tricky - specially if you have some non-daemon threads preventing jvm from shutting down. This might cause your test suite to hang. You can either use shutdownHoooks or use the spock AbstractGlobalExtension mechanism. You can actually execute some code right after spock executes all the specs. For our postgres scenario, we'd have something like:

class IntegrationTestCleanup extends AbstractGlobalExtension {

    @Override
    void stop() {
        IntegrationTest.destroy()
    }
}

There's one missing puzzle to make it work - you need to provide a special file under src/test/resources/META-INF.services/org.spockframework.runtime.extension.IGlobalExtension that references your extension. That file should contain a single line pointing to your extension, like com.example.IntegrationTestCleanup

This will make spock recognize it. Keep in mind it will be executed in dedicated spock thread.

I do realize it kind of duplicates the accepted answer, but I was recently struggling with performing a global cleanup in spock so I thought it could be useful.

Upvotes: 1

kriegaex
kriegaex

Reputation: 67327

The praise for the right answer belongs to Mark Bramnik when he wrote:

In general, Spock is a JUnit After all (that's why surefire plugin can run it without any additional hassle), so all approaches to the Suites should work

While this is the correct answer, the sample code in his answer refers to another scenario. So I am going to provide it here for reference. I do not expect this answer to get accepted, but if others read it they also should find sample code explaining how to use the feature in Spock:

Sample Spock tests:

package de.scrum_master.stackoverflow

class FooTest extends Specification {
  def test() {
    expect:
    println "FooTest"
  }
}
package de.scrum_master.stackoverflow

class BarTest extends Specification {
  def test() {
    expect:
    println "BarTest"
  }
}

Test suite:

package de.scrum_master.stackoverflow

import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.runner.RunWith
import org.junit.runners.Suite
import spock.lang.Specification

@RunWith(Suite.class)
@Suite.SuiteClasses([FooTest, BarTest])
class SampleTestSuite {
  @BeforeClass
  static void beforeTests() throws Exception {
    println "Before suite"
  }

  @AfterClass
  static void afterTests() throws Exception {
    println "After suite"
  }
}

Console log:

Before suite
FooTest
BarTest
After suite

Upvotes: 2

Mark Bramnik
Mark Bramnik

Reputation: 42491

In general, Spock is a JUnit After all (that's why surefire plugin can run it without any additional hassle), so all approaches to the Suites should work, although I haven't used this feature.

In addition, If you have a "heavy" shared resource, then you might try the following approach:

abstract class SharedResourceSupport extends Specification {
   def static sharedSource = new SharedSource()
}


class SharedSource {
    public SharedSource() {
        println "Created shared source. Its our heavy resource!"
    }
}


class SpockTest1 extends SharedResourceSupport {
    def "sample test" () {
      expect:
        sharedSource != null
    }
}

class SpockTest2 extends SharedResourceSupport {
    def "another test" () {
      expect:
        sharedSource != null
    }
}

Note that the shared resource is defined with "static", so that it will be created only once when the first test will access it.

As different strategies to deal with this: You might want to consider traits, if your test already inherits from another class, or expose shared resource as a Singleton so that it will guarantee that only one instance exists.

Try to run both tests and you'll see that that the line "Created shared source..." is called only once

Upvotes: 2

Related Questions