89
Smarter testing Java code with Spock Framework Including changes in Spock 1.0 Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/ Warszawa, 20th April 2015

4Developers 2015: Sprytniejsze testowanie kodu Java ze Spock Framework - Marcin Zajączkowski

  • Upload
    proidea

  • View
    119

  • Download
    3

Embed Size (px)

Citation preview

Smarter testing Java codewith Spock Framework

Including changes in Spock 1.0

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Warszawa, 20th April 2015

About me

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

2 interesting companies

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

AgendaWhy I like Spock?

Less know, but cool Spock features

New and noteworthy in Spock 1.0

Spock tricks

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Why idea for this presentation?JUnit/TestNG + Mockito + AssertJ work...

... but Spock brings testing to another level

Integrates seamlessly with production Java code

Since version 1.0 provides even more useful features

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Why idea for this presentation?JUnit/TestNG + Mockito + AssertJ work...

... but Spock brings testing to another level

Integrates seamlessly with production Java code

Since version 1.0 provides even more useful features

Encourage more people to using Spock

In even more effective way

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Why I like Spock?Consist and readable test code

Tests as specification by default

All Groovy magic available to help

Embedded mocking framework

Although Mockito can be used if preferred

Highly extensible

Compatible with tools supporting JUnit

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple specification in Spockgiven/when/then (or expect) are required to compile code

class SimpleCalculatorSpec extends Specification {

def "should add two numbers"() { given: def sut = new Calculator() when: def result = sut.add(1, 2) then: result == 3 }}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Conditional test exection

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@RequiresRuns annotated test (or the whole specification) only ifgiven criteria are met

Useful to depend on JVM version, operating system,activated profile, custom system property, etc.

Opposite to @IgnoreIf

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@RequiresRuns annotated test (or the whole specification) only ifgiven criteria are met

Useful to depend on JVM version, operating system,activated profile, custom system property, etc.

Opposite to @IgnoreIf

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

1.0

@Requires - continuedProvides access to system properties and environmentvariables

@Requires({ System.getProperty("os.arch") == "amd64" }) def "should use optimization on 64-bit systems"() { ... }

@Requires({ env.containsKey("ENABLE_CRM_INTEGRATION_TESTS") }) def "should do complicated things with CRM"() { ... }

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@Requires - continuedCan use static methods

import static FancyFeatureRequiredIntranetConnectionSpecIT.isUrlAvailable

@Requires({ isUrlAvailable("http://some-host.intranet/") })class FancyFeatureRequiredIntranetConnectionSpecIT extends Specification {

//could be also from trait or other class static boolean isUrlAvailable(String url) { //... }

def "should do one thing in intranet"() { ... }

def "should do another thing in intranet"() { ... }

...}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@Requires - continuedCan use static methods

import static FancyFeatureRequiredIntranetConnectionSpecIT.isUrlAvailable

@Requires({ isUrlAvailable("http://some-host.intranet/") })class FancyFeatureRequiredIntranetConnectionSpecIT extends Specification {

//could be also from trait or other class static boolean isUrlAvailable(String url) { //... }

def "should do one thing in intranet"() { ... }

def "should do another thing in intranet"() { ... }

...}

Import is required to prevent MissingMethodException

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@Requires - continuedCan use static methods

import static FancyFeatureRequiredIntranetConnectionSpecIT.isUrlAvailable

@Requires({ isUrlAvailable("http://some-host.intranet/") })class FancyFeatureRequiredIntranetConnectionSpecIT extends Specification {

@Memoized //could be also from trait or other class static boolean isUrlAvailable(String url) { //... }

def "should do one thing in intranet"() { ... }

def "should do another thing in intranet"() { ... }

...}

@Memoized for cachingMarcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@IgnoreIf and @Requires - new featuresspock.util.environment.Jvm

Simple and convenient access to Java version(java.version and java.specification.version)

boolean isJava7() //true only if Java 7boolean isJava8Compatible() //true if Java 8+

String getJavaVersion() //e.g. "1.8.0_05"String getJavaSpecificationVersion() //e.g. "1.8"

@IgnoreIf({ !jvm.java8Compatible })def "should find at runtime and use CompletableFuture for Java 8+"() { ... }

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@IgnoreIf and @Requires - new featuresspock.util.environment.Jvm

Simple and convenient access to Java version(java.version and java.specification.version)

boolean isJava7() //true only if Java 7boolean isJava8Compatible() //true if Java 8+

String getJavaVersion() //e.g. "1.8.0_05"String getJavaSpecificationVersion() //e.g. "1.8"

@IgnoreIf({ !jvm.java8Compatible })def "should find at runtime and use CompletableFuture for Java 8+"() { ... }

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

1.0

@IgnoreIf and @Requires - new featuresspock.util.environment.OperatingSystem

Simple and convenient access to OS information (os.name and os.version system properties)

String getName() //Linux String getVersion() //8.1 Family getFamily() //SOLARIS (enum)

boolean isLinux() boolean isWindows() boolean isMacOs() //also Solaris and Other boolean iSolaris() boolean isOther()

@Requires({ os.linux || os.macOs }) def "should use fancy console features"() { ... }

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@IgnoreIf and @Requires - new featuresspock.util.environment.OperatingSystem

Simple and convenient access to OS information (os.name and os.version system properties)

String getName() //Linux String getVersion() //8.1 Family getFamily() //SOLARIS (enum)

boolean isLinux() boolean isWindows() boolean isMacOs() //also Solaris and Other boolean iSolaris() boolean isOther()

@Requires({ os.linux || os.macOs }) def "should use fancy console features"() { ... }

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

1.0

Cleaning up

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@RestoreSystemPropertiesRestores original system properties

From System.getProperties()

To a state before test

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@RestoreSystemProperties - example@Stepwiseclass RestoreSystemPropertiesSpec extends Specification {

@RestoreSystemProperties def "should deny perform action when running as root"() { given: System.setProperty("user.name", "root") and: def sut = new DangerousActionPerformer() when: sut.squashThemAll() then: thrown(IllegalStateException) }

def "should perform action when running as 'normal' user"() { given: def sut = new DangerousActionPerformer() when: sut.squashThemAll() then: noExceptionThrown() }}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@RestoreSystemPropertiesRestores original system properties

From System.getProperties()To a state before test

Useful in integration tests when playing with themE.g. testing Spring profiles activation based on systemproperty

Two modesFor one feature (test)For the whole specification

Just like using for every test

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@RestoreSystemPropertiesRestores original system properties

From System.getProperties()To a state before test

Useful in integration tests when playing with themE.g. testing Spring profiles activation based on systemproperty

Two modesFor one feature (test)For the whole specification

Just like using for every test

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

1.0

@AutoCleanupCleans up resource at the end of its life time

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@AutoCleanup - with separate cleanupclass FancyDBSpec extends Specification {

@Shared private DBConnection dbConnection = new H2DBConnection(...)

def cleanupSpec() { dbConnection.release() }

def "should do fancy things on DB"() { ... }

def "should do fancy2 things on DB"() { ... }}

interface DBConnection {

... void release();}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@AutoCleanup - exampleclass FancyDBSpec extends Specification {

@AutoCleanup("release") @Shared private DBConnection dbConnection = new H2DBConnection(...)

def "should do fancy things on DB"() { ... }

def "should do fancy2 things on DB"() { ... }}

interface DBConnection {

... void release();}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@AutoCleanupCleans up resource at the end of its life time

Applied on field

No need to use separate cleaup/cleanupSpec method

Supports both instance (per feature) and shared (per specification) variables

By default method close() is called

Can be overridden with @AutoCleanup("release")

@AutoCleanup(quiet = true) to suppress exception logging

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

@AutoCleanupCleans up resource at the end of its life time

Applied on field

No need to use separate cleaup/cleanupSpec method

Supports both instance (per feature) and shared (per specification) variables

By default method close() is called

Can be overridden with @AutoCleanup("release")

@AutoCleanup(quiet = true) to suppress exception logging

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

0.7

Initialization and clean up in testsetup/cleanup can be declared per feature

useful if feature specific operations

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Initialization and clean up in testsetup/cleanup can be declared per feature

useful if feature specific operations

def "complex logic should work with fixed thread pool"() { setup: //alias for 'given:' ExecutorService threadPool = Executors.newFixedThreadPool(1) when: String returnedValue = threadPool .submit({ "val" } as Callable) .get(1, TimeUnit.SECONDS) then: returnedValue == "val" cleanup: threadPool?.shutdown()}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Initialization and clean up in testsetup/cleanup can be declared per feature

useful if feature specific operations

setup alias for given

cleanup like finally executed even on exception

works for every iteration (where)

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Native support for JUnit before/afterannotations

By default setupSpec(), setup(), cleanup(), cleanupSpec()

In addition @BeforeClass, @Before, @After, @AfterClass

Can be mixed together (also through class hierarchy)

No longer called twice in super class

More than one method of given type can be provided

Useful when dealing with

Additional testing frameworks like in Grails

Traits with setup/clean up code

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

1.0

Simplifying specifications with traitsShare common logic across different specifications

No specification inheritance involved

Can provide setup/cleanup methods

Can have instance variables

Groovy 2.3+ is required

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Groovy

2.3

Simplifying specifications with traitstrait DatabaseTrait {

@Shared DBConnection dbConnection

def setupSpec() { //or @BeforeClass //prepare DB }

def cleanupSpec() { //or @AfterClass //clean up DB }}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simplifying specifications with traitstrait DatabaseTrait {

@Shared DBConnection dbConnection

def setupSpec() { //or @BeforeClass //prepare DB }

def cleanupSpec() { //or @AfterClass //clean up DB }}

class Fancy1DatabaseSpec extends Specification implements DatabaseTrait { def "should do fancy1 thing on database"() { //fancy1 things on database }}

class Fancy2DatabaseSpec extends Specification implements DatabaseTrait { def "should do fancy2 thing on database"() { //fancy2 things on database }}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - basic casedef "should add two integers"() { given: Calculator sut = new SimpleCalculator() when: int result = sut.add(x, y) then: result == expectedResult where: x | y || expectedResult 1 | 2 || 3 -2 | 3 || 1 -1 | -2 || -3}

build-in support with where keyword

does not stop on failure for given test case

syntatic sugar for table-like data formatting

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - could look betterprivate PESELValidator sut = new FakePESELValidator()

def "should validate PESEL correctness"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - could look betterprivate PESELValidator sut = new FakePESELValidator()

def "should validate PESEL correctness"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - could look betterprivate PESELValidator sut = new FakePESELValidator()

def "should validate PESEL correctness"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

one combined test for all test cases

not very readable in case of failure(s)

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll@Unrolldef "should validate PESEL correctness (multiple tests)"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll@Unrolldef "should validate PESEL correctness (multiple tests)"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll@Unrolldef "should validate PESEL correctness (multiple tests)"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

@Unroll to generate separate test per test case

still confusing in case of failure

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll + #pesel@Unrolldef "should validate PESEL (#pesel) correctness (#isValid)"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll + #pesel@Unrolldef "should validate PESEL (#pesel) correctness (#isValid)"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll + #pesel@Unrolldef "should validate PESEL (#pesel) correctness (#isValid)"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true}

#pesel and #isValid to put input parameter into test report

more readable, but still can be confusing for businesspeople reading BDD reports

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll with message@Unroll("PESEL '#pesel' should be #description")def "should validate PESEL correctness"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true

description = isValid ? "valid" : "invalid"}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll with message@Unroll("PESEL '#pesel' should be #description")def "should validate PESEL correctness"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true

description = isValid ? "valid" : "invalid"}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - @Unroll with message@Unroll("PESEL '#pesel' should be #description")def "should validate PESEL correctness"() { expect: sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true

description = isValid ? "valid" : "invalid"}

distinguish test name and test descriptionused in test reportadditional (artificial) parameter for test report onlywith autocompletion in IntelliJ IDEA

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - data pipes@Unroll("PESEL '#pesel' should be #description")def "should validate PESEL correctness"() { expect: sut.validate(pesel) == isValid where: pesel << ["123", "abcd", "97110208631"] isValid << [false, false, true]

description = isValid ? "valid" : "invalid"}

table is syntatic sugar for data pipes

"<<" operator to connect data variable with data provider

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Parametrized tests - more data providers@Unroll("#pesel is valid (#dbId)")def "should validate PESEL correctness (CSV)"() { expect: sut.validate(pesel) where: [dbId, _, _, pesel] << readValidPeopleFromCSVFile() .readLines().collect { it.split(',') } //ugly way to read CSV - don't do this}

not only static elements

everything iterable in Groovy

also SQL rows or CSV files

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Mocking

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple Stubbingclass DaoSpec extends Specification {

def "should stub method call"() { given: Dao dao = Stub(Dao) dao.getCount() >> 1 expect: dao.getCount() == 1 }}

interface Dao { int getCount() Item save(Item item)}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple Stubbing - inlineclass DaoSpec extends Specification {

private Dao<Item> dao = Stub(Dao) { dao.getCount() >> 1 }

def "should stub method call"() { expect: dao.getCount() == 1 }}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple Stubbing - consecutive callsclass DaoSpec extends Specification {

def "should stub consecutive calls"() { given: Dao<Item> dao = Stub(Dao) dao.getCount() >> 1 >> 2 expect: dao.getCount() == 1 and: dao.getCount() == 2 and: dao.getCount() == 2 }}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple Stubbing - consecutive calls (list)class DaoSpec extends Specification {

def "should stub consecutive calls (list)"() { given: Dao<Item> dao = Stub(Dao) dao.getCount() >>> [1, 2] expect: dao.getCount() == 1 and: dao.getCount() == 2 and: dao.getCount() == 2 }}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple Stubbing - input parameters (array)class DaoSpec extends Specification {

def "should stub method call to return input value"() { given: Item baseItem = new Item() and: Dao<Item> dao = Stub(Dao) dao.save(_) >> { it[0] } when: Item returnedItem = dao.save(baseItem) then: baseItem.is(returnedItem) }}

by default array of input parameters

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple Stubbing - named input parametersclass DaoSpec extends Specification {

def "should stub method call to return input value"() { given: Item baseItem = new Item() and: Dao<Item> dao = Stub(Dao) dao.save(_) >> { Item item -> item } when: Item returnedItem = dao.save(baseItem) then: baseItem.is(returnedItem) }}

can be declared explicit as named (and typed) parameters

all have to be declared

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple Stubbing - throwing exceptionclass DaoSpec extends Specification {

def "should throw exception for specific input parameters"() { given: Dao<Item> dao = Stub(Dao) dao.save(_) >> { Item item -> throw new IllegalArgumentException(item.toString()) } when: dao.save(new Item()) then: thrown(IllegalArgumentException) }}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Simple Stubbing - throwing exceptionclass DaoSpec extends Specification {

def "should throw exception for specific input parameters"() { given: Dao<Item> dao = Stub(Dao) dao.save(_) >> { Item item -> throw new IllegalArgumentException(item.toString()) } when: dao.save(new Item()) then: thrown(IllegalArgumentException) when: dao.save(null) then: thrown(IllegalArgumentException) }}

smart toString() from Groovy (for null objects)

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Stubbing and verifying togetherclass DaoSpec extends Specification {

def "should stub and verify"() { given: Item baseItem = new Item() and: Dao<Item> dao = Stub(Dao) when: Item returnedItem = dao.save(baseItem) then: 1 * dao.save(_) >> { Item item -> item } and: baseItem.is(returnedItem) }}

has to be in the same statement

in then section

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Overriding stubbed valueclass BusinessServiceSpec extends Specification {

private BoringCollaborator boringStub = Stub(BoringCollaborator) { sendPing() >> "OK" } private BusinessService sut = new BusinessService(boringStub)

def "should calculate important business case 1"() { when: int result = sut.calculateImportantThings() then: result == EXPECTED_RESULT_1 }

def "should calculate important business case 2"() { ... }

many positive cases with sendPing() >> "OK"

how to test negative scenario?

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Overriding stubbed value - brokenclass BusinessServiceSpec extends Specification {

private BoringCollaborator boringStub = Stub(BoringCollaborator) { sendPing() >> "OK" } private BusinessService sut = new BusinessService(boringStub)

def "should calculate important business case 1"() { ... } def "should calculate important business case 2"() { ... }

@Ignore("broken") def "should cover collaborator error"() { given: boringStub.sendPing() >> "ERROR" //does not work! when: sut.calculateImportantThings() then: thrown(IllegalStateException) }

override in Mockito style does not work

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Overriding stubbed value - in then sectionclass BusinessServiceSpec extends Specification {

private BoringCollaborator boringStub = Stub(BoringCollaborator) { sendPing() >> "OK" } private BusinessService sut = new BusinessService(boringStub)

def "should calculate important business case 1"() { ... } def "should calculate important business case 2"() { ... }

def "should cover collaborator error"() { when: sut.calculateImportantThings() then: boringStub.sendPing() >> "ERROR" //stubbing in "then", looks bad and: thrown(IllegalStateException) }

has to placed in then section

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Verification in order - issue@Ignore("broken")def "notification should be send after business operation (ignore order)"() { given: Dao dao = Mock() Notifier notifier = Mock() and: def sut = new BusinessService(dao, notifier) when: sut.processOrder() then: 1 * dao.saveOrder(_) //order does not matter 1 * notifier.sendNotification(_)}

order is not verified

notification could be send before saving order

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Verification in order - solutiondef "notification should be send after business operation"() { given: Dao dao = Mock() Notifier notifier = Mock() and: def sut = new BusinessService(dao, notifier) when: sut.processOrder() then: 1 * dao.saveOrder(_) then: 1 * notifier.sendNotification(_)}

mutiple then block are evaluated separately

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Verification in order - gotcha@Ignore("broken")def "notification should be send after business operation (with 'and')"() { given: Dao dao = Mock() Notifier notifier = Mock() and: def sut = new BusinessService(dao, notifier) when: sut.processOrder() then: 1 * dao.saveOrder(_) and: //'and' does not verify execution order 1 * notifier.sendNotification(_)}

and does not verify execution order

it is for documentation purposes only

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Miscellaneous

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Active condition waitingPoolingConditions

Active waiting for condition to pass - like in Awaitility

No more sleep(3000)

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Active condition waiting - exampledef "should receive packet after while"() { given: def conditions = new PollingConditions() def asynchronousMessageQueue = new DelayedDeliveryMessageQueueFacade() when: asynchronousMessageQueue.sendPing() then: conditions.eventually { assert asynchronousMessageQueue.numberOfReceivedPackets == 1 }}

With default values

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Active condition waiting - exampledef "should receive packet after while"() { given: def conditions = new PollingConditions(timeout: 2, initialDelay: 0.3) def asynchronousMessageQueue = new DelayedDeliveryMessageQueueFacade() when: asynchronousMessageQueue.sendPing() then: conditions.eventually { assert asynchronousMessageQueue.numberOfReceivedPackets == 1 }}

With custom configuration

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Active condition waiting - exampledef "should receive packet after while"() { given: def conditions = new PollingConditions(timeout: 2, initialDelay: 0.3) def asynchronousMessageQueue = new DelayedDeliveryMessageQueueFacade() when: asynchronousMessageQueue.sendPing() then: conditions.within(5) { assert asynchronousMessageQueue.numberOfReceivedPackets == 1 }}

withing(timeoutInSeconds) can override original timeout inspecific call

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Active condition waiting - exampledef "should receive packet after while"() { given: PollingConditions conditions = new PollingConditions() def asynchronousMessageQueue = new DelayedDeliveryMessageQueueFacade() when: asynchronousMessageQueue.sendPing() then: conditions.eventually { asynchronousMessageQueue.numberOfReceivedPackets == 1 }}

assert keyword can omitted when PollingConditions isdeclared explicit (not with def)

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Active condition waitingPoolingConditions

Active waiting for condition to pass - like in Awaitility

No more sleep(3000)

Ability to specify: timeout, initialDelay, delay and factor

assert keyword is (usually) required

Useful in multithreading code and asynchronous externalcalls (like message queues)

Cannot be used for interaction verification

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Active condition waitingPoolingConditions

Active waiting for condition to pass - like in Awaitility

No more sleep(3000)

Ability to specify: timeout, initialDelay, delay and factor

assert keyword is (usually) required

Useful in multithreading code and asynchronous externalcalls (like message queues)

Cannot be used for interaction verification

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

0.7

Asserting previous value (manual way)Check if/how given value was changed

def "should create new record when item is saved (manual way)"() { given: Dao dao = new DummyDao() and: int originalNumberOfRecords = dao.getCount() when: dao.save(new Item()) then: dao.getCount() == originalNumberOfRecords + 1}

interface Dao { int getCount() void save(Item item)}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Asserting previous value (Spock way)def "should create new record when item is saved"() { given: Dao dao = new DummyDao() when: dao.save(new Item()) then: dao.getCount() == old(dao.getCount()) + 1}

interface Dao { int getCount() void save(Item item)}

Spock generates code to keep the old value

Works with fields and methods

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

BDD-like documentationHighlight business context

Block description (when: "put element on stack")

@See("https://en.wikipedia.org/wiki/Spock")

@Issue("http://tinyurl.com/spock-issue260")

@Subject("TODO: przyklady")

@Title

@Narrative

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Optional runtime configurationConfigures Spock behavior

Injectable into run context and extensions

By default for

Runner

Report

Ability to create custom configuration when needed

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

0.7, 1.0

Optional runtime configuration~/.spock/SpockConfig.groovy

runner { include package.DefaultSpecificationBaseClass exclude package.IntegrationAnnotation optimizeRunOrder = true filterStackTrace = false}

report { enabled = true issueNamePrefix = "JLTN-" issueUrlPrefix = "https://github.com/Confitura/jelatyna/issues" logFileSuffix = ".log" ...}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

0.7, 1.0

Optional runtime configuration - locationReads from SpockConfig.groovy taken from:

-Dspock.configuration

Classpath

Spock home directory

-Dspock.user.home

$SPOCK_USER_HOME

~/.spock/

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

0.7, 1.0

Exception utilMakes assertions on nested exception causes easier

Taken after Grails

spock.util.Exceptions

Throwable getRootCause(Throwable exception)List<Throwable> getCauseChain(Throwable exception)

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Exception utilMakes assertions on nested exception causes easier

Taken after Grails

spock.util.Exceptions

Throwable getRootCause(Throwable exception)List<Throwable> getCauseChain(Throwable exception)

def "should throw business exception on communication error keeping original"() { when: sut.sendPing(TEST_REQUEST_ID) then: def e = thrown(CommunicationException) e.message == "Communication problem when sending request with id: 5" Exceptions.getRootCause(e).class == ConnectException}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Exception utilMakes assertions on nested exception causes easier

Taken after Grails

spock.util.Exceptions

Throwable getRootCause(Throwable exception)List<Throwable> getCauseChain(Throwable exception)

def "should throw business exception on communication error keeping original"() { when: sut.sendPing(TEST_REQUEST_ID) then: def e = thrown(CommunicationException) e.message == "Communication problem when sending request with id: 5" Exceptions.getRootCause(e).class == ConnectException}

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

1.0

ExtensibilityPowerful extension mechanism

Many of features implemented as built-in extensions

Most of already presented

Official extensions available as separate modules

Spring, Guice, Tapestry, Unitils

(Sometimes) easier to write one's own extension thanhave accepted pull request

JUnit rules (@Rule/@ClassRule) can be also used

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Summary (aka Why I like Spock?)Consist and readable test code

Tests as specification by default

All Groovy magic available to help

Embedded mocking framework

Although Mockito can be used if preferred

Highly extensible

Compatible with tools supporting JUnit

Marcin Zajączkowski @SolidSoftBlog Version: 1.11p-4d

Questions?

Marcin Zajączkowski

http://blog.solidsoft.info/ @SolidSoftBlog

[email protected]

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/

Thank you(and remember about the feedback!)

Marcin Zajączkowski

http://blog.solidsoft.info/ @SolidSoftBlog

[email protected]

Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/