Posted on by | Posted in Developer Blog, synyx Blog | Tagged , , ,

There are a number of reasons to use the Spock testing framework:

First, tests - specifications in Spock speak - written in Spock are well structured, expressive and therefore provide good readability. In addition, Spock has built-in features like data driven testing and interaction based testing (mocking). Data driven testing allows your test code to be reused, i.e. to be applied multiple times with different parameters.

Second, because Spock is a Groovy based DSL, specification code can become concise where the equivalent Java code would be overly verbose. For example, Groovy provides native syntax for maps, lists and regular expressions. Closure coercion helps providing stub implementations for one or more interface methods without having to write a stub class. As Groovy and Java can freely be mixed together you can use any Java based library you like, or use Groovy based libraries. For example the HTTPBuilder enhances the HttpComponents HttpClient by providing features like various builders & parsers and a streamlined REST client.

Also the Spring framework supports Groovy and - not surprisingly - Spring TestContext framework works well with Spock: application contexts can easily be made available to specifications via annotation, thus enabling integration testing at all levels.

Spock specifications can be run from an IDE just like normal JUnit tests and, last but not least, implementing them is a great opportunity to learn the Groovy language.

For demonstration purposes we'll create a very simple Spring Boot web application that responds with string "prime" or "not prime" dependant on a number given by a request parameter. In case of errors the string "error" should be sent back to the client. Then we'll create Spock specifications, both for unit and integration testing.

We start by defining a service interface, its implementation and a controller class:

src/main/java/prime/service/PrimeService.java

public interface PrimeService {

    boolean isPrime(int number);
}

src/main/java/prime/service/PrimeServiceImpl.java

@Service
public class PrimeServiceImpl implements PrimeService {

    @Override
    public boolean isPrime(int number) {
        if (number < 0) {
            throw new IllegalArgumentException("argument must not be negative");
        }

        if (number <= 2) {
            return number == 2 ? true : false;
        }

        for (int i = 2; i < Math.sqrt(number) + 1; i++) {
            if (number % i == 0) {
                return false;
            }
        }

        return true;
    }
}

src/main/groovy/prime/web/PrimeController.groovy

@RestController
class PrimeController {

    @Autowired PrimeService primeService;

    @ExceptionHandler(Exception)
    String handleError() {
        'error';
    }

    @RequestMapping('/prime')
    String isPrime(@RequestParam int n) {
        primeService.isPrime(n) ? 'prime' : 'not prime';
    }
}

Since the application is based on Spring Boot we also add this class...

src/main/java/prime/Application.java

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

... and a build script:

src/build.gradle

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.6.RELEASE")
  }
}

apply plugin: 'groovy'
apply plugin: 'spring-boot'

jar {
  baseName = 'prime'
  version = '0.10.0'
}

repositories {
  mavenCentral()
}

dependencies {
  compile("org.codehaus.groovy:groovy-all:2.3.6")
  compile("org.springframework.boot:spring-boot-starter-jetty")
  compile("org.springframework.boot:spring-boot-starter-web") {
    exclude module: "spring-boot-starter-tomcat"
  }

  testCompile("org.springframework.boot:spring-boot-starter-test")
  testCompile("org.spockframework:spock-core:0.7-groovy-2.0")
}

The Groovy plugin handles mixed Groovy and Java code in the project. Not only is our controller class written in Groovy; the specifications for unit and integration testing will be too.

If Groovy is used in production code we have to include the groovy-all dependency to the compile configuration, otherwise this dependency should be added to the testCompile configuration.

Now we write unit specifications which verify the correctness of service implementation and controller:

src/test/groovy/prime/service/PrimeServiceImplSpec.groovy

class PrimeServiceImplSpec extends Specification {

    PrimeServiceImpl sut = new PrimeServiceImpl();

    def "test if the given number is prime"() {
        expect:
        sut.isPrime(n) == prime

        where:
        n | prime
        0 | false
        1 | false
        2 | true
        3 | true
        4 | false
        5 | true
        6 | false
        7 | true
    }

    def "check method argument constraints"() {
        when:
        sut.isPrime(-1)

        then:
        def e = thrown(IllegalArgumentException)
        e.message == 'argument must not be negative'
    }
}

src/test/groovy/prime/web/PrimeControllerSpec.groovy

class PrimeControllerSpec extends Specification {

    def "returns string 'prime' when service detects prime number"() {
        int p = 3
        def stub = { it == p ? true : false }

        expect:
        new PrimeController(primeService: stub).isPrime(p) == 'prime'
    }

    def "returns 'not prime' when service detects non-prime number"() {
        int n = 4
        def stub = { it == n ? false : true }

        expect:
        new PrimeController(primeService: stub).isPrime(n) == 'not prime'
    }
}

The first feature method in PrimeServiceImplSpec shows how Spocks data driven testing concept works and in PrimeControllerSpec we create service stubs by closure coercion.
Spock does also provide a means for interaction based testing, i.e. mocking and stubbing.

Before we implement an integration specification to verify the applications behaviour, we have to add another dependency in the build script. The spock-spring dependency enables to use the Spring TestContext framework together with Spock which is required for our integration specification.

src/build.gradle

testCompile("org.spockframework:spock-spring:0.7-groovy-2.0")

In order to separate the long running integration specifications from the unit specifications, we modify the build script by defining a corresponding sourceSet, associated configurations and task. Integration testing can now be triggered with gradle integTest

src/build.gradle

sourceSets {
    integTest {
        compileClasspath += main.output + test.output
        runtimeClasspath += main.output + test.output
    }
}

configurations {
    integTestCompile.extendsFrom testCompile
    integTestRuntime.extendsFrom testRuntime
}

task integTest(type: Test) {
    testClassesDir = sourceSets.integTest.output.classesDir
    classpath = sourceSets.integTest.runtimeClasspath
}

src/integTest/groovy/prime/PrimeSpec.groovy

@ContextConfiguration(loader = SpringApplicationContextLoader, classes = Application)
@WebAppConfiguration
@IntegrationTest
class PrimeSpec extends Specification {

    @Value('${local.server.port}')
    int port;

    def "server answers with 'prime' or 'not prime' or 'error'"() {
        expect:
        "http://localhost:$port/prime?n=$n"
            .toURL().text == response

        where:
        n  | response
        23 | 'prime'
        42 | 'not prime'
        -1 | 'error'
    }
}

In PrimeSpec the Spring Boot annotation @IntegrationTest causes the embedded application server to start. As an alternative we could use MockMvc to verify application response. Integration testing with MockMvc doesn't require a running application server.

To summarize, the Spock testing famework is a good example how Groovy can help Java developers. By writing Spock specifications, your test code - whether on the unit oder the integration level - can become concise and expressive. Integration into the build process is easy and your favorite IDE will handle specifications just like regular JUnit tests.

Links:

SpockBasics - Anatomy of a Spock specification

Spock Framework Reference Documentation

Spock: Test Well and Prosper by Ken Kousen



Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.