Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

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"() { ... } ... }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

@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"() { ... }

Slide 20

Slide 20 text

@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"() { ... }

Slide 21

Slide 21 text

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"() { ... }

Slide 22

Slide 22 text

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+"() { ... }

Slide 23

Slide 23 text

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+"() { ... }

Slide 24

Slide 24 text

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+"() { ... }

Slide 25

Slide 25 text

//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

Slide 26

Slide 26 text

//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

Slide 27

Slide 27 text

//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

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

setup/cleanup

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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() }

Slide 32

Slide 32 text

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() }

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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() }

Slide 35

Slide 35 text

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() }

Slide 36

Slide 36 text

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(); }

Slide 37

Slide 37 text

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(); }

Slide 38

Slide 38 text

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()

Slide 39

Slide 39 text

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(); }

Slide 40

Slide 40 text

cleaup cleanupSpec close() @AutoCleanup("release") @AutoCleanup(quiet = true)

Slide 41

Slide 41 text

@Before @After setupSpec() setup() cleanup() cleanupSpec() @BeforeClass @Before @After @AfterClass

Slide 42

Slide 42 text

+---------------+ | Specification | +---------------+

Slide 43

Slide 43 text

+---------------+ | Specification | +---------------+ ^ | +---------------------+ | BaseIntegrationSpec | +---------------------+

Slide 44

Slide 44 text

+---------------+ | Specification | +---------------+ ^ | +---------------------+ | BaseIntegrationSpec | +---------------------+ ^ | +------------------+ | BaseDatabaseSpec | +------------------+

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

+---------------+ | Specification | +---------------+ ^ | +---------------------+ | BaseIntegrationSpec | +---------------------+ ^ ^ | | +------------------+ +----------------- + | BaseDatabaseSpec | | BaseWiremockSpec | +------------------+ +------------------+ ^ ^ ^ ^ | | | | | | | | +-------- + +---------+ +---------------+ +---------------+ | DbSpec1 | | DbSpec2 | | WiremockSpec1 | | WiremockSpec2 | +---------+ +---------+ +---------------+ +---------------+

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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 } }

Slide 53

Slide 53 text

trait DatabaseTrait { ... } trait ActiveMQTrait { ... } trait WireMockTrait { ... } class VerySpecificIntegrationSpec extends Specification implements DatabaseTrait, WireMockTrait { def "should do very specific integration things"() { ... } }

Slide 54

Slide 54 text

@AutoCleanup cleanup(Spec) @Shared static static

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

@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" }

Slide 59

Slide 59 text

@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" }

Slide 60

Slide 60 text

@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" } <<

Slide 61

Slide 61 text

@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 }

Slide 62

Slide 62 text

@Unroll

Slide 63

Slide 63 text

@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"() { ... } (...) }

Slide 64

Slide 64 text

//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"() { ... } (...) }

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

@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"() { ... } (...) }

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

class DaoSpec extends Specification { def "should stub consecutive calls" () { given: Dao dao = Stub(Dao) dao.getCount() >> 1 >> 2 expect: dao.getCount() == 1 and: dao.getCount() == 2 and: dao.getCount() == 2 } }

Slide 69

Slide 69 text

class DaoSpec extends Specification { def "should stub consecutive calls" () { given: Dao dao = Stub(Dao) dao.getCount() >>> [1, 2] expect: dao.getCount() == 1 and: dao.getCount() == 2 and: dao.getCount() == 2 } }

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

@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(_) }

Slide 80

Slide 80 text

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(_) }

Slide 81

Slide 81 text

@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

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

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)

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

def dao = Mock(Dao) //Groovy way

Slide 87

Slide 87 text

def dao = Mock(Dao) //Groovy way Dao dao = Mock(Dao) //Java way

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

No content

Slide 92

Slide 92 text

No content

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

interface Dao { Optional getMaybeById(long id) ... } def "should not fail on unstubbed call with Optional return type"() { given: Dao dao = Stub() when: dao.getMaybeById(5) then: noExceptionThrown() }

Slide 95

Slide 95 text

interface Dao { Optional getMaybeById(long id) ... } @Ignore("Broken - Optional is final") def "should not fail on unstubbed call with Optional return type"() { given: Dao dao = Stub() when: dao.getMaybeById(5) then: noExceptionThrown() } CannotCreateMockException: Cannot create mock for class java.util.Optional because Java mocks cannot mock final classes.

Slide 96

Slide 96 text

EmptyOrDummyResponse

Slide 97

Slide 97 text

EmptyOrDummyResponse null ZeroOrNullResponse

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

EmptyOrDummyResponse null ZeroOrNullResponse

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

No content

Slide 103

Slide 103 text

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) } }

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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()

Slide 107

Slide 107 text

def "should call real method with changed arguments"() { given: TacticalStation tsSpy = Spy() and: tsSpy.getTubeStatus(_) >> { int tubeNumber -> return callRealMethodWithArgs(tubeNumber + 1) } expect: ... } callRealMethodWithArgs()

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

assert when: ... then: assertCompatibilityWithEU49683Directive(order) } private void assertCompatibilityWithEU49683Directive(Order order) { assert order.field1 == ... assert order.field2 == ... }

Slide 112

Slide 112 text

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: ...

Slide 113

Slide 113 text

assert expect: GParsPool.withPool { ["1"].eachParallel CorrelationIdUpdater.wrapClosureWithId { assert it == "1" } }

Slide 114

Slide 114 text

assert expect: GParsPool.withPool { ["1"].eachParallel CorrelationIdUpdater.wrapClosureWithId { assert it == "1" } }

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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 ?.

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

@Stepwise

Slide 120

Slide 120 text

No content

Slide 121

Slide 121 text

No content

Slide 122

Slide 122 text

No content

Slide 123

Slide 123 text

No content

Slide 124

Slide 124 text

No content

Slide 125

Slide 125 text

No content

Slide 126

Slide 126 text

No content