spock
TRANSCRIPT
![Page 1: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/1.jpg)
@nklmish
Spock Testing made groovy!!
Quality is not an act, it is a habit
@nklmish
![Page 2: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/2.jpg)
@nklmish
Agenda
What
Basic & advanced features (hopefully enough to get you started)
Hands on labs.
![Page 3: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/3.jpg)
@nklmish
About me
Senior Software Engineer, Consultant and architect around JVM technology.
Speaker
Blog - http://nklmish.me
Slideshare- http://slideshare.net/nklmish
![Page 4: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/4.jpg)
@nklmish
What is Spock?
![Page 5: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/5.jpg)
@nklmish
Spock…Brings best practises(Junit, Jmock, RSpec) under one umbrella.
Testing & specification framework
Based on Groovy
Makes testing fun (readable & compact code)
Enterprise ready
Compatible (IDEs, continuous integration services & build tools)
Groovy goodies
![Page 6: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/6.jpg)
@nklmish
Basic
![Page 7: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/7.jpg)
@nklmish
Hello world spec.
Every specification must extend spock sepcificationYour specification name
class PersonSpec extends spock.lang.Specification { def “should increment adam's annual salary by 10%"() { given: Person adam = new Person() adam.setSalary(50_000) when: adam.grantAnnualBonus(“10%”) then: adam.annualSalary() == 55_000 } }
Feature method
Spock blocks
![Page 8: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/8.jpg)
@nklmish
Multiple assertions
Multiple assertions (if any of these condition is false then test will fail)
class PersonSpec extends spock.lang.Specification { def “should increment adam's annual salary by 10%"() { given: Person adam = new Person() adam.setSalary(50_000) when: adam.grantAnnualBonus(“10%”) then: adam.annualSalary() == 55_000 adam.isAnnualBonusGranted() //assume it returns true !adam.isMonthlyBonusGranted() //assume it return false } }
![Page 9: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/9.jpg)
@nklmish
Instance fields e.g.
Instance field
class PersonSpec extends spock.lang.Specification { Person adam = new Person() def “should increment adam's annual salary by 10%"() { given: adam.setSalary(50_000) when: adam.grantAnnualBonus(“10%”) then: adam.annualSalary() == 55_000 adam.isAnnualBonusGranted() //assume it returns true !adam.isMonthlyBonusGranted() //assume it return false } }
![Page 10: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/10.jpg)
@nklmish
Instance fields
No sharing b/w feature methods => Every feature method get its own object
![Page 11: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/11.jpg)
@nklmish
What If I want to share an object b/w feature
methods?
![Page 12: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/12.jpg)
@nklmish
Use @Shared
field that can be shared b/w ALL test methods
class PersonSpec extends spock.lang.Specification { @Shared Person adam = new Person() def “should increment adam's annual salary by 10%"() { given: adam.setSalary(50_000) when: adam.grantAnnualBonus(“10%”) then: adam.annualSalary() == 55_000 adam.isAnnualBonusGranted() //assume it returns true !adam.isMonthlyBonusGranted() //assume it return false } }
![Page 13: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/13.jpg)
@nklmish
Shared fields
Allows to share an object.
Useful when object creation is expensive
![Page 14: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/14.jpg)
@nklmish
But what about static keyword to share an object
?
![Page 15: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/15.jpg)
@nklmish
static keyword
Use it to share constant values for e.g. static final FOO = 123
“@Shared” has well defined sharing semantics over “static”
![Page 16: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/16.jpg)
@nklmish
Spock Blocks
![Page 17: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/17.jpg)
@nklmish
Setup/given
given -> alias for ‘setup’ -> you can put some code that performs setup work
Non repeatable
![Page 18: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/18.jpg)
@nklmish
setup/given e.g.
setup block for a given test method
class LoanSpec extends spock.lang.Specification { @Shared Cache<Integer, Loan> cache = …// def “should find loan for a valid loan id"() { setup: Loan someLoan = new Loan() cache.put(5, someLoan)
//….. } def “should be able to create a new loan"() { setup: Loan loan = new Loan() //….. } }
![Page 19: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/19.jpg)
@nklmish
cleanup
Non repeatable
Runs no matter what( i.e. runs even if feature method has thrown an exception)
![Page 20: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/20.jpg)
@nklmish
cleanup e.g.
cleanup block for a given test method
class LoanSpec extends spock.lang.Specification { @Shared Cache<Integer, Loan> cache = …// def “should find loan for a valid loan id"() { setup: cache.put(5, someLoan) cleanup: cache.remove(5) //….. } }
![Page 21: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/21.jpg)
@nklmish
When, then & expect
when -> contains arbitrary code
then -> restricted to boolean conditions (assertions), exceptions, interactions & variable definitions.
expect -> restricted to boolean conditions and variable definitions.
Note : For purely functional methods “expect”, for side effect methods “when-then”.
![Page 22: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/22.jpg)
@nklmish
E.g.def “should be able to store elements into the cache” () { when: cache.put(5, someLoan)
then: cache.size() == 1
}
def “should be able to count all elements from the cache” () { expect: cache.count() == 10
}
def “should not be able to create a loan with negative amount” () { when: Loan loan = new Loan(amount:-100)
then: IllegalArgumentException ex = thrown() ex.message == ‘Loan can only be create with amount > 0’
}
![Page 23: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/23.jpg)
@nklmish
Assertion Helper method
def "should increment adam's annual salary by 10%"() { given: Person adam = new Person() and: adam.setSalary(50_000) when: adam.grantBonus(10) then: adam.annualSalary() == 55_000.0 adam.gotBonus()
adam.isHappy() adam.isChiefExecutive()
}
![Page 24: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/24.jpg)
@nklmish
Assertion Helper methoddef "should increment adam's annual salary by 10%"() { given: Person adam = new Person() and: adam.setSalary(50_000) when: adam.grantBonus(10) then: isBonusPaid(adam, 10_00_000) }
boolean isBonusPaid(Person person, BigDecimal expectedSalary) { person.annualSalary() == expectedSalary && person.gotBonus() &&
person.isHappy() && person.isChiefExecutive()
}
Note: We lost descriptive error message :(
![Page 25: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/25.jpg)
@nklmish
Assertion Helper methoddef "should increment adam's annual salary by 10%"() { given: Person adam = new Person() and: adam.setSalary(50_000) when: adam.grantBonus(10) then: isBonusPaid(adam, 10_00_000) }
void isBonusPaid(Person person, BigDecimal expectedSalary) { assert person.annualSalary() == expectedSalary assert person.gotBonus()
assert person.isHappy() assert person.isChiefExecutive() }
We have our descriptive message!!
![Page 26: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/26.jpg)
@nklmish
setup/cleanup at the beginning/end of life cycle
![Page 27: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/27.jpg)
@nklmish
E.g.
“quiet = true” => don't report exceptions . (default is true) “value” => method name to invoke on annotated object (default is ‘close’) Note : @Autocleanup can also be used on instance fields
class LoanSpec extends spock.lang.Specification { @AutoCleanup(quiet = true, value = “closeConnection”) @Shared private Database db
setupSpec() { db.populate() //load test data }
def “should increment adam's annual salary by 10%"() { setup: cache.load() //….. cleanup: cache.clear() } }
![Page 28: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/28.jpg)
@nklmish
Textually rich blocks
def "should find customer for a valid customer id"() {
given: 'an existing customer' def customer = new Customer(firstName: "Joe", dateOfBirth: now())
when: 'we request for customer with a valid id' def response = mockMvc.perform(get("/api/customers/1"))
then: 'we should receive client details' 1 * customerService.find(1) >> Optional.of(customer)
and: response.status == 200
}
and label can be used at any top-level
![Page 29: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/29.jpg)
@nklmish
Other features
![Page 30: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/30.jpg)
@nklmish
@Requiresimport spock.util.environment.OperatingSystem;class PersonSpec extends spock.lang.Specification {@Shared OperatingSystem os = OperatingSystem.newInstance() @Requires({env.containsKey(“HASH_KEY_TO_AUTHENTICATE”)})
def “should verify authentication token"() { } @Requires({os.isWindows() || isLegacyUser() }) def “should be able to authenticate via window’s credentials"() { } static boolean isLegacyUser() {//…} }
Run test ONLY if given condition(s) is/are met
![Page 31: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/31.jpg)
@nklmish
Data pipesclass HelloDataTableSpec extends spock.lang.Specification { def "total characters in name"() { expect: name.trim().size() == totalChars where: name << ["Joe "," doe"," hello "] totalChars << [3, 3, 5] }}
Data pipes connects data variable to data provider Data provider : Any iterable object in groovy (including csv and sql rows)
![Page 32: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/32.jpg)
@nklmish
Data tables, syntactic sugarclass HelloDataTableSpec extends spock.lang.Specification { def “should count total number of alphabets in name"() { expect: name.trim().size() == totalChars where: name || totalChars "Joe " || 3 " doe" || 3 " hello " || 5 } }
![Page 33: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/33.jpg)
@nklmish
Data tables failure reporting
class HelloDataTableSpec extends spock.lang.Specification { def “should count total number of alphabets in name"() { expect: name.trim().size() == totalChars where: name || totalChars "Joe " || 3 " doe" || 3 " hello " || 50 } }
Can we do better ?
![Page 34: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/34.jpg)
@nklmish
Yes, using unroll@Unrollclass HelloDataTableSpec extends spock.lang.Specification { def “should count total number of alphabets in #name”() { expect: name.trim().size() == totalChars where: name || totalChars "Joe " || 3 " doe" || 3 " hello " || 50 } }
![Page 35: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/35.jpg)
@nklmish
Ignoring tests
@Ignore
IgnoreRest
@IgnoreIf
![Page 36: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/36.jpg)
@nklmish
@Ignore@Ignore(“don’t run any test in this specification”)class HelloDataTableSpec extends spock.lang.Specification {def “should count total number of alphabets in name"() { expect: name.trim().size() == totalChars where: name || totalChars "Joe " || 3 " doe" || 3 " hello " || 5 } }
![Page 37: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/37.jpg)
@nklmish
@Ignoreclass HelloDataTableSpec extends spock.lang.Specification { @Ignore(“don’t run only this specific test”) def “should count total number of alphabets in name"() { expect: name.trim().size() == totalChars where: name || totalChars "Joe " || 3 " doe" || 3 " hello " || 5 } }
![Page 38: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/38.jpg)
@nklmish
@IgnoreRest
def “It will be ignored”() {…}
@IgnoreRest def “It will run”() {…}
![Page 39: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/39.jpg)
@nklmish
@IgnoreIf
def “I will run no matter what”() {…}
@IgnoreIf(os.isWindows()) def “I ll not run on windows”() {…}
![Page 40: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/40.jpg)
@nklmish
Mocking & stubbing
![Page 41: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/41.jpg)
@nklmish
Spock MockingUses:
JDK dynamic proxies for mocking interfaces.
CGLIB proxies for mocking classes
Mock objects are lenient by default (default behaviour can be overridden via stubbing)
Default returns values are false, 0 or null (except Object.toString/hashCode/equals)
Note : Mock can be used for both mocking and stubbing whereas stubs can be used only for stubbing!!!
![Page 42: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/42.jpg)
@nklmish
Mocking E.g.
class LoanSpec extends spock.lang.Specification { LoanManager loanManager = new LoanManager() ValidatorManager validatorManager = Mock(ValidatorManager) RepositoryManager repoManager = Mock(RepositoryManager)
//… }
![Page 43: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/43.jpg)
@nklmish
Interaction E.g.class LoanSpec extends spock.lang.Specification { ValidatorManager validatorManager = Mock(ValidatorManager) RepositoryManager repoManager = Mock(RepositoryManager) LoanManager loanManager = new LoanManager(validatorManager : validatorManager, repositoryManager: repoManager) def "should consult with validator manager and repository manager before saving a new loan application"() { given:
Loan someLoan = new Loan() when:
loanManager.save(someLoan)
then: 1 * validatorManager.validate(someLoan) 1 * repoManager.save(someLoan) } } cardinality (how many method calls are expected)
target constraint (mock object)
method constraint (method you are interested in)
argument constraint (expected method argument)
![Page 44: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/44.jpg)
@nklmish
Invocation order
class LoanSpec extends spock.lang.Specification { ValidatorManager validatorManager = Mock(ValidatorManager) RepositoryManager repoManager = Mock(RepositoryManager) LoanManager loanManager = new LoanManager(validatorManager : validatorManager, repositoryManager: repoManager)
def "should consult with validator manager and repository manager before saving a new loan application"() { when: loanManager.save(someLoan)
then: 1 * validatorManager.validate(someLoan) then: 1 * repoManager.save(someLoan) } }
![Page 45: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/45.jpg)
@nklmish
Detecting Mock Obj
class LoanSpec extends spock.lang.Specification { ValidatorManager validatorManager = Mock(ValidatorManager) RepositoryManager repoManager = Mock(RepositoryManager) LoanManager loanManager = new LoanManager(validatorManager : validatorManager, repositoryManager: repoManager)
def "should consult with validator manager before saving a new loan application"() { when: loanManager.save(someLoan)
then: 1 * validatorManager.validate(someLoan) new MockDetector().isMock(validatorManager)
} }
You can get more info using MockDetector, like name, type, etc
![Page 46: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/46.jpg)
@nklmish
Stub
When you are ONLY interested in returning some value in respond to particular method call OR want to perform side effect.
![Page 47: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/47.jpg)
@nklmish
Stubbing
ValidatorManager validatorManager = Stub(ValidatorManager) def setup() { validatorManager.validate(_) >> true }
}
Note: this will always return true whenever validatorManager.validate() is invoked
![Page 48: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/48.jpg)
@nklmish
Stubbing, return list of values
ValidatorManager validatorManager = Stub(ValidatorManager) def setup() { validatorManager.validate >> [true,false,true] }
}
translates to: return true for first invocation, false for second and true for all other invocations
![Page 49: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/49.jpg)
@nklmish
Stubbing, throw exception + chaining responses
ValidatorManager validatorManager = Stub(ValidatorManager) def setup() { validatorManager.validate >> [true,false] >> {throw new RuntimeException()} >> [true] }
}
translates to: return true for first invocation, false for second and throw exception for third and return true for all other invocations
![Page 50: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/50.jpg)
@nklmish
Summary
Builtin mocking framework
Descriptive failure message
Readable tests
Groovy goodies
Extensible
![Page 51: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/51.jpg)
@nklmish
Lab Exercises
![Page 52: Spock](https://reader036.vdocuments.net/reader036/viewer/2022062904/5876050b1a28ab4a508b66af/html5/thumbnails/52.jpg)
@nklmish
Thank you !