Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Interesting nooks and crannies of Spock you (may) have never seen before

Interesting nooks and crannies of Spock you (may) have never seen before

I bet that Spock needs not to be introduced to anyone here. For many of us it was an entry point to the thrilling world of Groovy. Spock has a flat learning curve - a sensible Java programmer (without prior Groovy experience) can start writing Spock specifications within several minutes. However, when you combine Groovy & Spock magic, a new fascinating meta-world is created. The exploration of its every nook and cranny can take much longer (and it's definitely much more efficient with a guide ;-).

You can expect a set of more advanced (and/or less known) techniques and mechanisms, the use of which can even more simplify and facilitate the testing of our code. You have to be forewarned that we will also visit those dark areas where it is better not to enter, things that do not work or work in a way nobody expects.

Spoiler. It is a Groovy focused version of my warmly welcomed "Smarter testing Java code with Spock Framework" presentation, enriched with additional tricks and traps.

Marcin Zajączkowski

June 02, 2016
Tweet

More Decks by Marcin Zajączkowski

Other Decks in Programming

Transcript

  1. 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"() { ... } ... }
  2. 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"() { ... } ... } MissingMethodException
  3. 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
  4. @Requires({ System.getProperty("os.arch") == "amd64" }) //old way def "should use

    optimization on 64-bit systems"() { ... } @Requires({ System.getenv().containsKey("ENABLE_CRM_INTEGRATION_TESTS") }) //old way def "should do complicated things with CRM"() { ... }
  5. @Requires({ System.getProperty("os.arch") == "amd64" }) //old way def "should use

    optimization on 64-bit systems"() { ... } @Requires({ System.getenv().containsKey("ENABLE_CRM_INTEGRATION_TESTS") }) //old way def "should do complicated things with CRM"() { ... }
  6. spock.util.environment.OperatingSystem os.name os.version String getName() //Linux String getVersion() //8.1 Family

    getFamily() //SOLARIS (enum) boolean isLinux() boolean isWindows() boolean isMacOs() boolean isSolaris() boolean isOther() @Requires({ os.linux || os.macOs }) def "should use fancy console features"() { ... }
  7. spock.util.environment.Jvm java.version java.specification.version boolean isJava7() //true only if Java 7

    boolean isJava8Compatible() //true if Java 8+ String getJavaVersion() //e.g. "1.8.0_05" String getJavaSpecificationVersion() //e.g. "1.8" @IgnoreIf({ !jvm.java8Compatible }) def "should return empty Optional by default for unstubbed methods with Java 8+"() { ... }
  8. spock.util.environment.Jvm java.version java.specification.version boolean isJava7() //true only if Java 7

    boolean isJava8Compatible() //true if Java 8+ String getJavaVersion() //e.g. "1.8.0_05" String getJavaSpecificationVersion() //e.g. "1.8" @IgnoreIf({ !jvm.java8Compatible }) def "should return empty Optional by default for unstubbed methods with Java 8+"() { ... }
  9. spock.util.environment.Jvm java.version java.specification.version boolean isJava7() //true only if Java 7

    boolean isJava8Compatible() //true if Java 8+ String getJavaVersion() //e.g. "1.8.0_05" String getJavaSpecificationVersion() //e.g. "1.8" @IgnoreIf({ !jvm.java8Compatible }) def "should return empty Optional by default for unstubbed methods with Java 8+"() { ... }
  10. //from trait or util class private static final PreconditionContext XXX

    = new PreconditionContext() @IgnoreIf({ !XXX.jvm.java8Compatible }) def "should return empty Optional by default for unstubbed methods with Java 8+"() { ... } @Requires({ XXX.os.linux || XXX.os.macOs }) def "should use fancy console features"() { ... } PreconditionContext
  11. //from trait or util class private static final PreconditionContext XXX

    = new PreconditionContext() @IgnoreIf({ !XXX.jvm.java8Compatible }) def "should return empty Optional by default for unstubbed methods with Java 8+"() { ... } @Requires({ XXX.os.linux || XXX.os.macOs }) def "should use fancy console features"() { ... } PreconditionContext
  12. //from trait or util class private static final PreconditionContext XXX

    = new PreconditionContext() @IgnoreIf({ !XXX.jvm.java8Compatible }) def "should return empty Optional by default for unstubbed methods with Java 8+"() { ... } @Requires({ XXX.os.linux || XXX.os.macOs }) def "should use fancy console features"() { ... } PreconditionContext
  13. setup/cleanup 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() } where
  14. setup/cleanup def "complex logic should work with fixed thread pool"()

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

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

    setup: ExecutorService threadPool = Executors.newFixedThreadPool(1) and: Callable task = { "val" } as Callable when: String returnedValue = threadPool .submit(task) .get(1, TimeUnit.SECONDS) //null is returned! then: returnedValue == "val" cleanup: threadPool?.shutdown() } Runnable Callable
  17. def "complex logic should work with fixed thread pool"() {

    setup: ExecutorService threadPool = Executors.newFixedThreadPool(1) and: Callable task = [call: { "val" }] as Callable when: String returnedValue = threadPool .submit(task) .get(1, TimeUnit.SECONDS) then: returnedValue == "val" cleanup: threadPool?.shutdown() }
  18. class FancyDBSpec extends Specification { @Shared private DBConnection dbConnection def

    beforeSpec() { dbConnection = new H2DBConnection(...) } def cleanupSpec() { dbConnection.close() } def "should do fancy things on DB"() { ... } def "should do fancy2 things on DB"() { ... } } interface DBConnection { ... void close() }
  19. class FancyDBSpec extends Specification { @Shared private DBConnection dbConnection =

    new H2DBConnection(...) def cleanupSpec() { dbConnection.close() } def "should do fancy things on DB"() { ... } def "should do fancy2 things on DB"() { ... } } interface DBConnection { ... void close(); }
  20. class FancyDBSpec extends Specification { @Shared private DBConnection dbConnection =

    new H2DBConnection(...) def cleanupSpec() { dbConnection.close() } def "should do fancy things on DB"() { ... } def "should do fancy2 things on DB"() { ... } } interface DBConnection { ... void close(); }
  21. class FancyDBSpec extends Specification { @Shared @AutoCleanup private DBConnection dbConnection

    = new H2DBConnection(...) def "should do fancy things on DB"() { ... } def "should do fancy2 things on DB"() { ... } } interface DBConnection { ... void close(); } close()
  22. class FancyDBSpec extends Specification { @Shared @AutoCleanup("release") private DBConnection dbConnection

    = new H2DBConnection(...) def "should do fancy things on DB"() { ... } def "should do fancy2 things on DB"() { ... } } interface DBConnection { ... void release(); }
  23. +---------------+ | Specification | +---------------+ ^ | +---------------------+ | BaseIntegrationSpec

    | +---------------------+ ^ | +------------------+ | BaseDatabaseSpec | +------------------+
  24. +---------------+ | Specification | +---------------+ ^ | +---------------------+ | BaseIntegrationSpec

    | +---------------------+ ^ | +------------------+ | BaseDatabaseSpec | +------------------+ ^ ^ | | | | +-------- + +---------+ | DbSpec1 | | DbSpec2 | +---------+ +---------+
  25. +---------------+ | Specification | +---------------+ ^ | +---------------------+ | BaseIntegrationSpec

    | +---------------------+ ^ ^ | | +------------------+ +----------------- + | BaseDatabaseSpec | | BaseWiremockSpec | +------------------+ +------------------+ ^ ^ ^ ^ | | | | | | | | +-------- + +---------+ +---------------+ +---------------+ | DbSpec1 | | DbSpec2 | | WiremockSpec1 | | WiremockSpec2 | +---------+ +---------+ +---------------+ +---------------+
  26. +---------------+ | Specification | +---------------+ ^ | +---------------------+ +--------------------------- +

    | BaseIntegrationSpec |<-------| BaseWiremockWithDbSpec ??? | +---------------------+ +----------------------------+ ^ ^ | | +------------------+ +----------------- + | BaseDatabaseSpec | | BaseWiremockSpec | +------------------+ +------------------+ ^ ^ ^ ^ | | | | | | | | +-------- + +---------+ +---------------+ +---------------+ | DbSpec1 | | DbSpec2 | | WiremockSpec1 | | WiremockSpec2 | +---------+ +---------+ +---------------+ +---------------+
  27. +---------------+ | Specification | +---------------+ ^ | +---------------------+ +--------------------------- +

    | BaseIntegrationSpec |<-------| BaseWiremockWithDbSpec ??? | +---------------------+ +----------------------------------+ ^ ^ | BaseWiremockWithActiveMqSpec ??? | | | +----------------------------------+ +------------------+ +----------------- + | BaseDatabaseSpec | | BaseWiremockSpec | +------------------+ +------------------+ ^ ^ ^ ^ | | | | | | | | +-------- + +---------+ +---------------+ +---------------+ | DbSpec1 | | DbSpec2 | | WiremockSpec1 | | WiremockSpec2 | +---------+ +---------+ +---------------+ +---------------+
  28. +---------------+ | Specification | +---------------+ ^ | +---------------------+ +--------------------------- +

    | BaseIntegrationSpec |<-------| BaseWiremockWithDbSpec ??? | +---------------------+ +----------------------------------+ ^ ^ | BaseWiremockWithActiveMqSpec ??? | | | +----------------------------------+ +------------------+ +----------------- + | BaseDatabaseSpec | | BaseWiremockSpec | +------------------+ +------------------+ ^ ^ ^ ^ | | | | | | | | +-------- + +---------+ +---------------+ +---------------+ | DbSpec1 | | DbSpec2 | | WiremockSpec1 | | WiremockSpec2 | +---------+ +---------+ +---------------+ +---------------+
  29. trait DatabaseTrait { static DBConnection dbConnection def setupSpec() { //or

    @BeforeClass or inline //prepare DB } def cleanupSpec() { //or @AfterClass //clean up DB } }
  30. trait DatabaseTrait { static DBConnection dbConnection def setupSpec() { //or

    @BeforeClass or inline //prepare DB } def cleanupSpec() { //or @AfterClass //clean up DB } } //no more infrastructure code needed in specifications 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 } }
  31. trait DatabaseTrait { ... } trait ActiveMQTrait { ... }

    trait WireMockTrait { ... } class VerySpecificIntegrationSpec extends Specification implements DatabaseTrait, WireMockTrait { def "should do very specific integration things"() { ... } }
  32. @Unroll def "should validate PESEL (#pesel) correctness (#isValid)"() { expect:

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

    sut.validate(pesel) == isValid where: pesel || isValid "123" || false "abcd" || false "97110208631" || true } #pesel #isValid
  34. @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" }
  35. @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" }
  36. @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" } <<
  37. @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 }
  38. @Unroll @Unroll @Unroll class PeselValidatorSpec extends Specification { def "should

    pass valid PESEL #number"() { ... } def "should reject too short PESEL #number"() { ... } def "should pass Mr. President PESEL"() { ... } (...) }
  39. //dependency in Gradle/Maven/Ant testCompile 'info.solidsoft.spock:spock-global-unroll:0.5.1' @Unroll class PeselValidatorSpec extends Specification

    { //unrolled automatically without @Unroll def "should pass valid PESEL #number"() { ... } (...) }
  40. @Roll @DisableGlobalUnroll @Roll class PeselValidatorSpec extends Specification { //one big

    test for multiple input parameters def "should not be unrolled for some reasons PESEL #number"() { ... } (...) }
  41. @Unroll @Roll @Roll class PeselValidatorSpec extends Specification { //one big

    test for multiple input parameters def "should not be unrolled for some reasons PESEL #number"() { ... } //unrolled anyway @Unroll("PESEL '#pesel' should be #description") def "should validate PESEL correctness"() { ... } (...) }
  42. class 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 } }
  43. class 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 } }
  44. class DaoSpec extends Specification { def "should stub consecutive calls

    (exception)"() { given: Dao<Item> dao = Stub(Dao) dao.getCount() >> 1 >> { throw new IllegalArgumentException() } >> 3 expect: dao.getCount() == 1 when: dao.getCount() then: thrown(IllegalArgumentException) expect: dao.getCount() == 3 }
  45. class DaoSpec extends Specification { def "should stub consecutive calls

    (exception)"() { given: Dao<Item> dao = Stub(Dao) dao.getCount() >> 1 >> { throw new IllegalArgumentException() } >> 3 expect: dao.getCount() == 1 when: dao.getCount() then: thrown(IllegalArgumentException) expect: dao.getCount() == 3 }
  46. class DaoSpec extends Specification { def "should stub consecutive calls

    (exception)"() { given: Dao<Item> dao = Stub(Dao) dao.getCount() >> 1 >> { throw new IllegalArgumentException(???) } >> 3 expect: dao.getCount() == 1 when: dao.getCount() then: thrown(IllegalArgumentException) expect: dao.getCount() == 3 }
  47. class Dao<T> { T save(T element) } class DaoSpec extends

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

    with one parameter"() { given: Item baseItem = new Item() and: Dao<Item> dao = Stub() dao.save(_) >> { it } //broken - 'it' is an array - it[0] when: Item returnedItem = dao.save(baseItem) then: baseItem.is(returnedItem) } }
  49. class DaoSpec extends Specification { def "should stub method call

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

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

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

    { given: Item baseItem = new Item() and: Dao<Item> dao = Mock() when: Item returnedItem = dao.save(baseItem) then: 1 * dao.save(_) >> { Item item -> item } and: baseItem.is(returnedItem) } } then
  53. @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(_) }
  54. def "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(_) }
  55. @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
  56. 1 * tacticalStation.fireTorpedo(2) //equal to 2 1 * tacticalStation.fireTorpedo(!2) //not

    equal to 2 1 * tacticalStation.fireTorpedo(_) //any argument 1 * scienceStation.analyzeInput(!null) //not null argument 1 * scienceStation.analyzeInput(_ as String) //instance of String 1 * tacticalStation.fireTorpedo({ it > 2 }) //any argument greater than 2 1 * ts.smellyFindNumberOfShipsInRangeByCriteria(_, { it.contains("fiant") }, 4)
  57. 1 * tacticalStation.fireTorpedo(2) //equal to 2 1 * tacticalStation.fireTorpedo(!2) //not

    equal to 2 1 * tacticalStation.fireTorpedo(_) //any argument 1 * scienceStation.analyzeInput(!null) //not null argument 1 * scienceStation.analyzeInput(_ as String) //instance of String 1 * tacticalStation.fireTorpedo({ it > 2 }) //any argument greater than 2 1 * ts.smellyFindNumberOfShipsInRangeByCriteria(_, { it.contains("fiant") }, 4) ts.smellyFindNumberOfShipsInRangeByCriteria(_, { it.contains("fiant") }, 4) >> 2
  58. def dao = Mock(Dao) //Groovy way Dao dao = Mock(Dao)

    //Java way Dao dao = Mock() //shorter Java way?
  59. def dao = Mock(Dao) //Groovy way Dao dao = Mock(Dao)

    //Java way Dao dao = Mock() //shorter Java way
  60. def dao = Mock(Dao) //Groovy way Dao dao = Mock(Dao)

    //Java way Dao dao = Mock() //shorter Java way
  61. interface Dao<T> { Optional<T> getMaybeById(long id) ... } def "should

    not fail on unstubbed call with Optional return type"() { given: Dao<Order> dao = Stub() when: dao.getMaybeById(5) then: noExceptionThrown() }
  62. interface Dao<T> { Optional<T> getMaybeById(long id) ... } @Ignore("Broken -

    Optional is final") def "should not fail on unstubbed call with Optional return type"() { given: Dao<Order> dao = Stub() when: dao.getMaybeById(5) then: noExceptionThrown() } CannotCreateMockException: Cannot create mock for class java.util.Optional because Java mocks cannot mock final classes.
  63. EmptyOrDummyResponse null ZeroOrNullResponse def "should not fail on unstubbed call

    with Optional return type - workaround 1"() { given: Dao<Order> dao = Stub([defaultResponse: ZeroOrNullResponse.INSTANCE]) when: dao.getMaybeById(5) then: noExceptionThrown() }
  64. EmptyOrDummyResponse null ZeroOrNullResponse def "should not fail on unstubbed call

    with Optional return type - workaround 2"() { given: Dao<Order> dao = Mock() //instead of Stub() when: dao.getMaybeById(5) then: noExceptionThrown() }
  65. EmptyOrDummyResponse null ZeroOrNullResponse def "should not fail on unstubbed call

    with Optional return type - workaround 2"() { given: Dao<Order> dao = Mock() //instead of Stub() when: dao.getMaybeById(5) then: noExceptionThrown() } Optional
  66. class Java8EmptyOrDummyResponse implements IDefaultResponse { public static final Java8EmptyOrDummyResponse INSTANCE

    = new Java8EmptyOrDummyResponse(); private Java8EmptyOrDummyResponse() {} @Override public Object respond(IMockInvocation invocation) { if (invocation.getMethod().getReturnType() == Optional) { return Optional.empty() } //possibly CompletableFutures.completedFuture(), dates and maybe others return EmptyOrDummyResponse.INSTANCE.respond(invocation) } }
  67. defaultResponse Stub def "should return empty Optional for unstubbed calls"()

    { given: Dao<Order> dao = Stub([defaultResponse: Java8EmptyOrDummyResponse.INSTANCE]) when: Optional<Order> result = dao.getMaybeById(5) then: result?.isPresent() == false //NOT the same as !result?.isPresent() }
  68. defaultResponse Stub def "should return empty Optional for unstubbed calls"()

    { given: Dao<Order> dao = Stub([defaultResponse: Java8EmptyOrDummyResponse.INSTANCE]) when: Optional<Order> result = dao.getMaybeById(5) then: result?.isPresent() == false //NOT the same as !result?.isPresent() } Dao<Order> dao = Stub8(Dao) <T> T Stub8(Class<T> clazz) { return Stub([defaultResponse: Java8EmptyOrDummyResponse.INSTANCE], clazz) }
  69. def "should change real method execution return value"() { given:

    TacticalStation tsSpy = Spy() and: tsSpy.getTubeStatus(_) >> { if (it[0] == 4) { return TubeStatus.BROKEN } else { return callRealMethod() } } expect: tsSpy.getTubeStatus(1) == TubeStatus.LOADED and: tsSpy.getTubeStatus(4) == TubeStatus.BROKEN } callRealMethod()
  70. def "should call real method with changed arguments"() { given:

    TacticalStation tsSpy = Spy() and: tsSpy.getTubeStatus(_) >> { int tubeNumber -> return callRealMethodWithArgs(tubeNumber + 1) } expect: ... } callRealMethodWithArgs()
  71. Assertion failed: assert word.substring(begin, end) == word[begin..end] | | |

    | | | || | | po 1 3 | | |1 3 Spock | | poc | Spock false assert then expect
  72. Assertion failed: assert word.substring(begin, end) == word[begin..end] | | |

    | | | || | | po 1 3 | | |1 3 Spock | | poc | Spock false assert then expect
  73. assert when: ... then: assertCompatibilityWithEU49683Directive(order) } private void assertCompatibilityWithEU49683Directive(Order order)

    { assert order.field1 == ... assert order.field2 == ... } given given: int initialNumberOfFiles = getNumberOfFileInDirectory() assert initialNumberOfFiles == 0 when: ...
  74. def "should show nice error message on null dereference"(){ when:

    Person readPerson = dao.getById(TEST_WOMAN) then: readPerson.sex == Sex.WOMAN ... }
  75. def "should show nice error message on null dereference"(){ when:

    Person readPerson = dao.getById(TEST_WOMAN) then: readPerson.sex == Sex.WOMAN ... } java.lang.NullPointerException: Cannot get property 'sex' on null object ?.
  76. def "should show nice error message on null dereference"(){ when:

    Person readPerson = dao.getById(TEST_WOMAN) then: readPerson?.sex == Sex.WOMAN ... } readPerson?.sex == Sex.WOMAN | | | null | false null
  77. def "should show nice error message on null dereference"(){ when:

    Person readPerson = dao.getById(TEST_WOMAN) then: readPerson.sex == Sex.WOMAN //wihtout '?.' ... } java.lang.NullPointerException: Cannot invoke method startsWith() on null object readPerson.sex == Sex.WOMAN | | | null | false java.lang.NullPointerException: Cannot get property 'sex' on null object