Vyacheslav Rusakov
Vyacheslav Rusakov's Blog

Vyacheslav Rusakov's Blog

Testing Spock 2 extensions

Vyacheslav Rusakov's photo
Vyacheslav Rusakov
·Jan 10, 2022·

4 min read

This is the follow-up of Testing JUnit 5 extensions article (assume you read it first). This article shows how to test Spock 2 extensions the same way.

Like JUnit's Jupiter, Spock 2 is implemented as JUnit platform engine and so TestKit might be used for running Spock tests too. But there are a few differences.

First of all, Spock already provides a shortcut for TestKit tests: spock.util.EmbeddedSpecRunner.

As with JUnit, suppose we want to check extension test method fail handling (and extension should stop this exception type):

@MySpockExtension
class extends Specification {

    void "test method fail"() {

        when:
        throw new IllegalStateException("Ups")

        then:
        true
    }
}
def specRunner = new EmbeddedSpecRunner()
specRunner.throwFailure = false
specRunner.runClass(FailingTestCase)
          .testEvents()
          .debug() // optional 
          .assertStatistics(stats -> stats.failed(1));

By default, EmbeddedSpecRunner re-throws exceptions which is not desirable (have to disable it for the same behavior as in pure JUnit kit tests).

Also, runner supports direct test compilation so the test itself could be described as a string instead of a separate class:

def specRunner = new EmbeddedSpecRunner()
specRunner.throwFailure = false
specRunner.run '''
package com.foo

import spock.lang.Specification
import spock.util.EmbeddedSpecRunner

@MySpockExtension
class extends Specification {

    void "test method fail"() {

        when:
        throw new IllegalStateException("Ups")

        then:
        true
    }
}
'''
          .testEvents()
          .debug() // optional 
          .assertStatistics(stats -> stats.failed(1));

This may look weird, but, probably, could be useful in some cases. At least, this solves the "failing tests problem" because the test in a string would never be recognized and executed directly. Still, I think separate test classes usage is handier.

You can find more EmbeddedSpecRunner usage examples in spock sources.

Failing tests problem

As with JUnit tests, failing test cases (used for TestKit tests) would be found and executed directly and fail the build. Spock does not support JUnit constraints and so @Disabled can't be used to skip "test scenarios" from separate execution. Spock @Ignore extension does not provide a way to disable for TestKit execution and that's why I have to use the following hack:

static Boolean ACTIVE = false with @Requires spock extension:

@Requires({ AbstractTest.ACTIVE })
class TestCase extends Specification { ...

Where AbstractTest.ACTIVE is set to true before each TestKit test (full source below) and reverted to false after.

You can evolve this approach for your case if required.

Testing parallel execution

As in JUnit, concurrent execution of test methods might be configured with @Execution(ExecutionMode.CONCURRENT) class annotation. IMPORTANT this is Spock annotation, not JUnit one (classes are the same just in different packages).

Executing several tests in parallel:

def specRunner = new EmbeddedSpecRunner()
specRunner.throwFailure = false
specRunner.configurationScript {
         runner {
             parallel {
                 enabled true
                 defaultExecutionMode ExecutionMode.CONCURRENT
                 defaultSpecificationExecutionMode ExecutionMode.CONCURRENT
             }
         }
}
specRunner.runClasses([Test1, Test2, Test3, Test4])
      .testEvents()
      .debug() // optional
      .assertStatistics(stats -> stats.succeeded(4));

WARNING: there should not be variables called the same as sections inside configurationScript (for example, runner, parallel, etc.) otherwise you'll see very non-intuitive errors.

In the example above both specs and spec methods would run in parallel, but you can configure differently. Also, note that for data-driven tests (with where section) each iteration is a different execution and so iterations could run in parallel too.

Simple testing technique

The base test from JUnit article would look like this for Spock:

abstract class AbstractTest extends Specification {

    // used for activating tests only for TestKit run
    static Boolean ACTIVE = false

    List<String> runTest(Class test) {
        ActionHolder.cleanup()
        ACTIVE = true
        try {
            def specRunner = new EmbeddedSpecRunner()
            // do not rethrow exception - all errors will remain in holder
            specRunner.throwFailure = false
            specRunner.runClass(test)
                    .allEvents().failed().stream()
                    // exceptions appended to events log
                    .forEach(event -> {
                        Throwable err = event.getPayload(TestExecutionResult.class).get().getThrowable().get();
                        ActionHolder.add("Error: (" + err.getClass().getSimpleName() + ") " + err.getMessage())
                    })
            return ActionHolder.getState()
        } finally {
            ACTIVE = false
            ActionHolder.cleanup()
        }
    }
}

Here, as in JUnit example, all execution errors are collected as string messages, so when anything goes wrong the list of the resulting strings would be different.

Complete example case for testing parameters injection (assuming extension injects required parameter):

@Requires({ AbstractTest.ACTIVE })
@MySpockExtension
class ExceptionCase extends Specification {

    def "Sample test"(Integer arg) {

        when:
        ActionHolder.add("test.body $arg")
        throw new IllegalStateException('Ups')

        then:
        true
    }
}

And the main TestKit test:

class SpockTest extends AbstractTest {

    def "Check parameter"() {

        expect: 'test fails'
        runTest(ExceptionCase) == [
                                  "test.body 11",
                                  "Error: (IllegalStateException) Ups"]
    }
}
 
Share this