GR8 Conf US: Spock Soup to Nuts

2,557 views

Published on

An overview of everything that is cool in the Spock Testing Framework

Published in: Technology, Education
0 Comments
9 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,557
On SlideShare
0
From Embeds
0
Number of Embeds
69
Actions
Shares
0
Downloads
56
Comments
0
Likes
9
Embeds 0
No embeds

No notes for slide
  • - Introduce you to spock and spock nomenclature\n- Show you how do data drive your tests\n- Go over testing your object interactions with Spock Mocks\n- Show you some of the built in Extensions\n- And finally show you how to roll your own\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • - runs before every feature method\n- @Before (JUnit 4)\n- void setup() (JUnit 3)\n\n\n
  • - runs after every feature method\n- @After (JUnit 4)\n- void tearDown() (JUnit 3)\n
  • - runs before the first feature method \n- @BeforeClass (JUnit 4)\n
  • - runs after the last feature method\n- @AfterClass (JUnit 4)\n
  • - Another structure you can use to test is the ‘expect’ block\n- ‘When’ and ‘Then’ block rolled up into one\n- Best for testing functional behavior that has no side effects\n\n\n
  • \n
  • Power asserts become very powerful \n\nwhen you start dealing with complex object.\n\nprovide a wealth of insight as to what is going on with out firing up a debugger.\n
  • Problems with this:\n\n- Code and data are mixed together and can’t be changed independantly\n- once one of the assertions fail the whole test will exit, not running anything after the failure\n\nWhat we want to do is separate our data from our code and have each set of data run in isolation for all other sets\n\nThis is where Spock Paramerterizations come in to play\n\n\n
  • \n\nUsing the ‘where’ block and Data Pipes\n\nIntroduce Where Block & Data Pipes\n\nData Pipes connect a data variable to a Data Provider\nData Provider can be anything that has a size() method and implements Iterator.\n - Collection, String, custom.\n\n\n
  • - On the left we have the test name but no real indication what test failed.\n- Smart assertion shows us Class of the objects that are being compared are the reason for failure\n
  • Plain @Unroll gives us individual test output, w/ an zero base ordinal position of the test\n
  • \n
  • - @Unroll with a value gives us individual test output with as much details of the test and paramaters as we want.\n\n
  • \n
  • - The other way to get the details ouput of the parameterized tests is to use the templaing in the method name.\n\n- My preference is to put the test name output in the annotation and kept the method name in tact for human readability\n\nData Pipes are nice but the can get a bit unwieldy if you have a lot of scenerios. This is where data table come in.\n
  • - Data Tables\n- cleaner, more human readable\n- Table format adds clarity and readibility to your parameterized tests\n- can use double pipe to add a visible break between input params and output params\n- IntellJ will apply formating to make your table look pretty\n\nThere is one more way we can create a Data Provider for our test and that is writing a custom data provider\n
  • - multiple databinding\n- can be used to get data from db, excel file; or to randomally generate data for test\n
  • - hasNext()\n- next()\n- size()\n- close() is called but can be a no opt if not needed, useful for db\n
  • - Like mockito mock are lenient by default. This means that unexpected method calls on mock objects are allowed returning the default value.\n\n- Default return values are based on the methods return type and will be (false, 0 , or null)\n- Mocks are equal only to itself, and has a unique hash code, toString() includes the name of the type it represents.\n
  • Two way to create a mock\n\nMock() is a method on the Specification superclass\n\ndynamic\n\nstatic\n\n
  • Two way to create a mock\n\nMock() is a method on the Specification superclass\n\ndynamic\n\nstatic\n\n
  • Global vs. Local Scoping\n\nOutside of ‘when’ block = global = valid from point of definition to end of feature method\n\nInside of ‘then’ block = local = valid only from the preceding ‘when’ block\n\n
  • Global vs. Local Scoping\n\nOutside of ‘when’ block = global = valid from point of definition to end of feature method\n\nInside of ‘then’ block = local = valid only from the preceding ‘when’ block\n\n
  • Global vs. Local Scoping\n\nOutside of ‘when’ block = global = valid from point of definition to end of feature method\n\nInside of ‘then’ block = local = valid only from the preceding ‘when’ block\n\nDon’t mix the two up. Local will overwrite global and can cause some confusion in your expected output.\n\nKen Kousin \n\n
  • Optional vs. Required\n\nOptional = no cardinality must have an explicit return value\n - act as more of a Stub where we are interested in what controlling what is coming out of this method\n - not woried about the verifying that the method is called\n\nRequired = has cardinality and may have a return value\n- more of a true mock where we are interested in the verifying the behavior of the system\n
  • Optional vs. Required\n\nOptional = no cardinality must have an explicit return value\n - act as more of a Stub where we are interested in what controlling what is coming out of this method\n - not woried about the verifying that the method is called\n\nRequired = has cardinality and may have a return value\n- more of a true mock where we are interested in the verifying the behavior of the system\n
  • Ananomy of a interaction\n
  • Ananomy of a interaction\n
  • Cardnality\n\nexactly ‘n’ number of times\n\n
  • Cardnality\n\nexplicit range\n\n
  • Cardnality\n\nat least 3 times with an wildcarded upper bound\n\n
  • Cardnality\n\nat most 3 times with an wildcarded lower bound\n\n
  • InteractionNotSatisfiedError\n
  • Target Constraint\n\nCan be for a specific mock instance\n\n
  • Target Constraint\n\nCan be for a specific mock instance\n\n
  • Target Constraint\n\nCan be for a specific mock instance\n\n
  • Target Constraint\n\nCan be for a specific mock instance\n\n
  • Target Constraint\n\nor a wild carded (all mocks)\n
  • Target Constraint\n\nor a wild carded (all mocks)\n
  • Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  • Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  • Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  • Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  • Method Constraints\n\nRegEx.\n\n
  • Method Constraints\n\nRegEx.\n\n
  • Method Constraints\n\nCan be wild carded for a call to that method signature on any mock object\n\n
  • Method Constraints\n\nCan be wild carded for a call to that method signature on any mock object\n\n
  • Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  • Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  • Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  • Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  • Method Constraints\n\nwildcards: Any 2 arguments\n\n
  • Method Constraints\n\n*_ matches any argument list\n\n
  • Method Constraints\n\nAny param that is an instance of a Class\n\n\n\n
  • Method Constraints\n\nSecond param is any non-null value\n\n\n\n
  • Method Constraints\n\ncustom param as a closure\n\n\n
  • Return Values\n\nStubs: that provide a return value for the mock call\n\nNeed to have a return value if you make the interaction optional by not have a cardnality.\n\n
  • Return Values\n\nStubs: that provide a return value for the mock call\n\nNeed to have a return value if you make the interaction optional by not have a cardnality.\n\n
  • Return Values\n\nStubs: that provide a return value for the mock call\n\nNeed to have a return value if you make the interaction optional by not have a cardnality.\n\n
  • Return Values\n\nStubs: that provide a return value for the mock call\n\nNeed to have a return value if you make the interaction optional by not have a cardnality.\n\n
  • Return Values\n\nReturn values can be in be itererated over for each call to it.\n\nLast in list will be called indefinitely \n\n\n\n
  • Return Values: Custom\n\n\n\n\n\n
  • Return Values: Custom Action\n\nStub out the throwing of an error\n\n\n\n\n\n
  • Return Values: Chained\n\nInteractions are NOT ordered\n\nIf declared in the same ‘then’ block the interactions enforcement of order dose not exist\n\nCan override this with multiple THEN blocks and putting interactions within\n\n\n\n\n\n
  • Return Values: Chained\n\nInteractions are NOT ordered\n\nIf declared in the same ‘then’ block the interactions enforcement of order dose not exist\n\nCan override this with multiple THEN blocks and putting interactions within\n\n\n\n\n\n
  • If declared in the same ‘then’ block the interactions enforcement of order dose not exist\n\nCan override this with multiple THEN blocks and putting interactions within\n
  • If declared in the same ‘then’ block the interactions enforcement of order dose not exist\n\nCan override this with multiple THEN blocks and putting interactions within\n\nWrong Invocation Order Exception\n
  • Extensions are m\n
  • Type and Method annotation\n\nUnrolls the paramertized test and replaces the test name as\n
  • Field Level only annotation\n\nPrimarily used for marking variables outside of a feature (ie. setup()) that will be used between iterations.\n\ncan achieve the same thing by marking it static.\n\nwill be shared by all test methods\n
  • Type and Method level annotation\n\nInvoked on the regular test framework thread\nThe the timer runs out an SpockTimeoutError will be thrown\n\nValue is an integer\nDefault unit is TimeUnit.SECONDS\njava.util.concurrent.TimeUnit\n\n
  • Type Only annotation\n\nIndicates that all feature methods should be run sequentially from the top down\n\nif one method fails the remaining methods will be skipped\n\n
  • Type & Method annotation\n\nIndicates the method should not be run\n\n
  • Method Only annotation\n\nIndicates that all other feature methods should be skipped.\n\nCan set this on multiple methods\nHandy for running a single test in isolation\n
  • Method & Class annotation\n\nTakes a closure that should return something that resolves to a truthy Groovy statement.\n\n\n\n\n
  • Method & Class annotation\n\nTakes a Throwable class that the SUT is expected to throw\n\n\n
  • Field annotation\n\nBy default calles the ‘close’ method on the object that it is annotated during the cleanup phase of the test.\n\nEquivilate of calling close in cleanup\n\nquite = true will not report any exceptions that are thrown during the execution of the method.\n\n\n\n\n
  • Type & Method annotation\n\nActivate one or more groovy Categories while the class or feature method executes\n\n\n\n
  • A Do-Nothing class that get methods dynamically added to it a runtime\n\nThis is done all the time in Grails (GORM). Plugins do it.\n\nSooner or later your going to run into it.\n\nMostly solved with intergration tests :(\n\n\n\n\n
  • \n\n\n\n
  • \n\n\n\n
  • \n\n\n\n
  • If you want to learn more about Categories Ken Kousen is giving a talk about metaprograming for fun an profit right after this talk\n\n\n\n
  • Type and Method\n\nConfines the changes made to the metaclasses of the specified class to the annotated scope\n\nLifecycle:\nfor a method\n- on setup the existing classes meta classes is removed and an a new temporay one is added and registered.\n- on cleanup the original metaclass is restored\nfor a spec/classe\n- on setupSpec\n- on cleanupSpec\n\nHelpful for preventing test pollution where the outcomes of test are different when they are run together vs. individually.\n\n\n\n
  • \n
  • \n
  • Using the Spock API to roll your own extensions\n\n2 types\n- Method interceptors via the extending org.spockframework.runtime.extension.AbstractMethodInterceptor.java\n- intercept method invocation\n- invocation.proceed\n- can change test outcome or behavior\n- can throw exceptions\n- Runtime Listener via org.spockframework.runtime.AbstractRunListener\n- Listen passively\n- Can Inspect the tests and test results\n- Can be used to programatically skipp a spec or feature\n- Should not throw errors\n\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Using a runtime listener\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • GR8 Conf US: Spock Soup to Nuts

    1. 1. Spock
    2. 2. Zan Thrash
    3. 3. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         Book book = new Book(isdn:book & a library" given: "I have a checked out 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         controller.catalogService = new CatalogService()         params.id = library.id for controller" and: "all dependancies         params.isdn = book.isdn and: "we submit the isdn and library id"         controller.returnBook()     JSONElement json = response.getJson()         assertEquals( json.isdn, book.isdn ) when: "submit the form"         assertEquals( json.status, FILED )     }} then: "verify the returned book is FILED" json.isdn == book.isdn json.status == FILED
    4. 4. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         Book book = new Book(isdn:book & a library" given: "I have a checked out 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         controller.catalogService = new CatalogService()         params.id = library.id for controller" and: "all dependancies         params.isdn = book.isdn and: "we submit the isdn and library id"         controller.returnBook()     JSONElement json = response.getJson()         assertEquals( json.isdn, book.isdn ) when: "submit the form"         assertEquals( json.status, FILED )     }} then: "verify the returned book is FILED" json.isdn == book.isdn json.status == FILED
    5. 5. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         Book book = new Book(isdn:book & a library" given: "I have a checked out 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         controller.catalogService = new CatalogService()         params.id = library.id for controller" and: "all dependancies         params.isdn = book.isdn and: "we submit the isdn and library id"         controller.returnBook()     JSONElement json = response.getJson()         assertEquals( json.isdn, book.isdn ) when: "submit the form"         assertEquals( json.status, FILED )     }} then: "verify the returned book is FILED" json.isdn == book.isdn json.status == FILED
    6. 6. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         given: "I have a checked out book & a library"         Book book = new Book(isdn: 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         and: "all dependancies for controller"         controller.catalogService = new CatalogService() params.id = library.id and: "we submit the isdn and library id"         params.isdn = book.isdn     controller.returnBook()         JSONElement json = response.getJson() when: "submit the form"             assertEquals( json.isdn, book.isdn ) assertEquals( json.status, FILED ) then: "verify the returned book is FILED" } json.isdn == book.isdn json.status == FILED}
    7. 7. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         given: "I have a checked out book & a library"         Book book = new Book(isdn: 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         and: "all dependancies for controller"         controller.catalogService = new CatalogService() params.id = library.id and: "we submit the isdn and library id"         params.isdn = book.isdn     controller.returnBook()         JSONElement json = response.getJson() when: "submit the form"             assertEquals( json.isdn, book.isdn ) assertEquals( json.status, FILED ) then: "verify the returned book is FILED" } json.isdn == book.isdn json.status == FILED}
    8. 8. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         given: "I have a checked out book & a library"         Book book = new Book(isdn: 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         and: "all dependancies for controller"         controller.catalogService = new CatalogService() and: "we submit the isdn and library id"         params.id = library.id     params.isdn = book.isdn         when: "submit the form"         controller.returnBook()     JSONElement json = response.getJson() then: "verify the returned book is FILED" json.isdn == book.isdn assertEquals( json.isdn, book.isdn ) assertEquals( json.status, FILED ) json.status == FILED }}
    9. 9. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         given: "I have a checked out book & a library"         Book book = new Book(isdn: 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         and: "all dependancies for controller"         controller.catalogService = new CatalogService() and: "we submit the isdn and library id"         params.id = library.id     params.isdn = book.isdn         when: "submit the form"         controller.returnBook()     JSONElement json = response.getJson() then: "verify the returned book is FILED" json.isdn == book.isdn assertEquals( json.isdn, book.isdn ) assertEquals( json.status, FILED ) json.status == FILED }}
    10. 10. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         given: "I have a checked out book & a library"         Book book = new Book(isdn: 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         and: "all dependancies for controller"         controller.catalogService = new CatalogService() and: "we submit the isdn and library id"         params.id = library.id     params.isdn = book.isdn         when: "submit the form"         controller.returnBook()     JSONElement json = response.getJson() then: "verify the returned book is FILED" json.isdn == book.isdn book.isdn ) assertEquals( json.isdn, json.status == FILED assertEquals( json.status, FILED ) }}
    11. 11. @TestFor(LibraryController)@Mock([Book, Library])class LibraryControllerTests extends Specification{ LibraryControllerSpec {     void testReturnBookToLibrary() { libraray"() { "sucessfully return book to         given: "I have a checked out book & a library"         Book book = new Book(isdn: 1234567ABC).save()         Library library = new Library(name: Carnegie).save()         and: "all dependancies for controller"         controller.catalogService = new CatalogService() and: "we submit the isdn and library id"         params.id = library.id     params.isdn = book.isdn         when: "submit the form"         controller.returnBook()     JSONElement json = response.getJson() then: "verify the returned book is FILED" json.isdn == book.isdn book.isdn ) assertEquals( json.isdn, json.status == FILED assertEquals( json.status, FILED ) }}
    12. 12. Fixture Methods
    13. 13. def setup() {}
    14. 14. def cleanup() {}
    15. 15. def setupSpec() {}
    16. 16. def cleanupSpec() {}
    17. 17. void "add 2 things together"() {    expect:        1 + 2 == 3 Bat + man == Bat man        99 + 1 == 100}
    18. 18. Condition not satisfied:Bat + man == Bat man      |       |      Batman  false              1 difference (85% similarity)              Bat(-)man              Bat( )man
    19. 19. Condition not satisfied:json.status == FILE|    |      ||    FILED  false|           1 difference (80% similarity)|           FILE(D)|           FILE(-)[id:1, author:null, title:UNKNOWN, status:FILED, class:opi.com.Book, isdn:1234567ABC
    20. 20. void "add 2 things together"() {    expect:        1 + 2 == 3 Bat + man == Bat man        99 + 1 == 100}
    21. 21. void "add 2 things together"() {    expect:"a + b = c"        a + b == c    where:        a << [1, Bat, 99]        b << [1, man, 1]        c << [2, Batman, 100]}
    22. 22. @Unrollvoid "add 2 things together"() {    expect:"a + b = c"        a + b == c    where:        a << [1, Bat, 99]        b << [1, man, 1]        c << [2, Batman, 100]}
    23. 23. @Unroll(#a + #b = #c)void "add 2 things together"() {    expect:"a + b = c"        a + b == c    where:        a << [1, Bat, 99]        b << [1, man, 1]        c << [2, Batman, 100]}
    24. 24. @Unrollvoid "#a + #b = #c"() {    expect:"a + b = c"        a + b == c    where:        a << [1, Bat, 99]        b << [1, man, 1]        c << [2, Batman, 100]}
    25. 25. @Unroll(#a + #b = #c)void "add 2 things together"() {    expect:"a + b = c"        a + b == c    where:        a      | b     || c        1      | 1     || 2        99     | 1     || 100        "Bat" | "man" || "Batman"}
    26. 26. void "custom data provider" () {    expect:"a + b = c"        a + b == c    where:        [a, b, c] << new ABCDataProvider()}
    27. 27. class ABCDataProvider implements Iterator{    List list = [[1,2,3], [1,99, 100], [Bat, man, Batman]]    int position = 0    boolean hasNext() {        position < list.size()    }    Object next() {        def result = list[position]        position += 1        result    }    def size() {        list.size()    }    def close(){ }    void remove() { }}
    28. 28. Interactions
    29. 29. def catalogService = Mock(CatalogService)CatalogService catalogService = Mock()
    30. 30. def catalogService = Mock(CatalogService)CatalogService catalogService = Mock()
    31. 31. void "test interaction scoping"() {    given:"create mock CatalogService"        CatalogService service = Mock()        controller.catalogService = service    and: "make sure it can file books"        service.isAvailable(_ as Book) >> true    when:"we verify isdn"        params.isdn = book.isdn        def result = controller.verifyISDN()    then:"verify the book was filed"        1 * service.inquired(_)        result == true}
    32. 32. void "test interaction scoping"() {    given:"create mock CatalogService"        CatalogService service = Mock()        controller.catalogService = service    and: "make sure it can file books"        service.isAvailable(_ as Book) >> true    when:"we verify isdn" Global        params.isdn = book.isdn        def result = controller.verifyISDN()    then:"verify the book was filed"        1 * service.inquired(_)        result == true}
    33. 33. void "test interaction scoping"() {    given:"create mock CatalogService"        CatalogService service = Mock()        controller.catalogService = service    and: "make sure it can file books"        service.isAvailable(_ as Book) >> true    when:"we verify isdn" Local        params.isdn = book.isdn        def result = controller.verifyISDN()    then:"verify the book was filed"        1 * service.inquired(_)        result == true}
    34. 34. void "test interaction scoping"() {    given:"create mock CatalogService"        CatalogService service = Mock()        controller.catalogService = service    and: "make sure it can file books"        service.isAvailable(_ as Book) >> true    when:"we verify isdn" Required        params.isdn = book.isdn        def result = controller.verifyISDN()    then:"verify the book was filed"        1 * service.inquired(_)        result == true}
    35. 35. void "test interaction scoping"() {    given:"create mock CatalogService"        CatalogService service = Mock()        controller.catalogService = service    and: "make sure it can file books"        service.isAvailable(_ as Book) >> true    when:"we verify isdn" Optional        params.isdn = book.isdn        def result = controller.verifyISDN()    then:"verify the book was filed"        1 * service.inquired(_)        result == true}
    36. 36. 1 * catalogService.file(book, library) >> FILED Carnality
    37. 37. 1 * catalogService.file(book, library) >> FILED Carnality
    38. 38. 1 * catalogService.file(book, library) >> FILED Carnality
    39. 39. (0..3) * catalogService.file(book, library) >> FILED Carnality
    40. 40. (3.._) * catalogService.file(book, library) >> FILED Carnality
    41. 41. (_..3) * catalogService.file(book, library) >> FILED Carnality
    42. 42. TooFewInvocationsErrorTooManyInvocationsError
    43. 43. (_..3) * catalogService.file(book, library) >> FILED Carnality Target Constraint
    44. 44. (_..3) * catalogService.file(book, library) >> FILED Carnality Target Constraint
    45. 45. (_..3) * _.file(book, library) >> FILED Target Constraint
    46. 46. (_..3) * _.file(book, library) >> FILED Target Constraint
    47. 47. (_..3) * catalogService.file(book, library) >> FILED Target Constraint Conststraint Method
    48. 48. (_..3) * catalogService.file(book, library) >> FILED Target Constraint Conststraint Method
    49. 49. (_..3) * catalogService./f.*/(book, library) >> FILED Method Conststraint
    50. 50. (_..3) * catalogService./f.*/(book, library) >> FILED Method Conststraint
    51. 51. (_..3) * catalogService._(book, library) >> FILED Method Conststraint
    52. 52. (_..3) * catalogService._(book, library) >> FILED Method Conststraint
    53. 53. (_..3) * catalogService.file(book, library) >> FILED Method Conststraint List Constraint Argument
    54. 54. (_..3) * catalogService.file(book, library) >> FILED Method Conststraint List Constraint Argument
    55. 55. (_..3) * catalogService.file(_, _) >> FILED Argument List Constraint
    56. 56. (_..3) * catalogService.file(*_) >> FILED Argument List Constraint
    57. 57. (_..3) * catalogService.file(_ as Book, _) >> FILED Argument List Constraint
    58. 58. (_..3) * catalogService.file(_ as Book, !null) >> FILED Argument List Constraint
    59. 59. (_..3) * catalogService.file({it.isdn > 1}, !null) >> FILED Argument List Constraint
    60. 60. 1 * catalogService.file(book, library) >> FILED Return Values Constraint Argument List
    61. 61. 1 * catalogService.file(book, library) >> FILED Return Values Constraint Argument List
    62. 62. 3 * catalogService.file(book, library) >>> [FILED,‘ERROR’] Return Values
    63. 63. 3 * catalogService.file(book, library) >> { book.status = FILED return book } Return Values
    64. 64. 3 * catalogService.file(book, library) >> { throw new TimeoutException() } Return Values
    65. 65. foo.bar() >> { throw new IOException() } >>> [1, 2, 3] >> { throw new RuntimeException() }  Return Values
    66. 66. (_.._) * _._(*_)
    67. 67. Order Is NOT enforced void "test ordered interactions"() {   ...     then:"verify the book was filed"         1 * service.inquired(_)         2 * service.notifyLibrarian(*_)         result == true }
    68. 68. Interactions are NOT orderedvoid "test ordered interactions"() {  ...    then:"verify an inquiry was add to the book"        1 * service.inquired(_) then:"verify that all librarians are notified "        2 * service.notifyLibrarian(*_)        result == true}
    69. 69. Extensions
    70. 70. @Unroll
    71. 71. @Shared
    72. 72. @Timeout() @Timeout(10)@Timeout(value=10, unit=TimeUnit.MILLISECONDS)
    73. 73. @Stepwise
    74. 74. @Ignore
    75. 75. @IgnoreRest
    76. 76. @IgnoreIf({ 1 })
    77. 77. @FailsWith(IOException)
    78. 78. @AutoCleanup()@AutoCleanup(‘dispose’)@AutoCleanup(quite=true)
    79. 79. @Use([Category])
    80. 80. class Dynamic {}
    81. 81. void "test the Use annotation"() {    when:"call awesomeness"        def result = new Dynamic().awesomeness(very)    then:"check result"        result == so very awesome}
    82. 82. class DynamicCategory {        static String awesomeness(Dynamic dynamic, String foo) {        “so $foo awesome”    }    }
    83. 83. @Use(DynamicCategory)void "test the Use annotation"() {    when:"call awesomeness"        def result = new Dynamic().awesomeness(very)    then:"check result"        result == so very awesome}
    84. 84. @Use(DynamicCategory)void "test the Use annotation"() {    when:"call awesomeness"        def result = new Dynamic().awesomeness(very)    then:"check result"        result == so very awesome}
    85. 85. void "test the Use annotation"() {    when:"call awesomeness" use(DynamicCategory){         def result = new Dynamic().awesomeness(very) }    then:"check result"        result == so very awesome}
    86. 86. @ConfineMetaClassChanges([Class])
    87. 87. ~/.spock/SpockConfig.groovyimport spock.util.mop.Useimport spock.lang.*import grails.plugin.spock.IntegrationSpecrunner {    optimizeRunOrder true    filterStackTrace false    include Unroll, Use    exclude {        baseClass IntegrationSpec    }}
    88. 88. >$ grails -Dspock.configuration=test/SpockConfig.groovy test-app
    89. 89. Rolling your ownSpock Extensions
    90. 90. import spock.lang.*@SayOnFailclass MyFirstSpec extends Specification {  @SayOnFail(value="Danger! Danger!", voice=Zarvox)  def "lets try this!"() {    expect:    Math.max(1, 2) == 3  }}
    91. 91. Step 1: Create Annotation@Retention(RetentionPolicy.RUNTIME)@Target([ElementType.TYPE, ElementType.METHOD])@ExtensionAnnotation(SayOnFailExtension)public @interface SayOnFail {    String value() default     String voice() default "Alex"}
    92. 92. Step 1: Create Annotation@Retention(RetentionPolicy.RUNTIME)@Target([ElementType.TYPE, ElementType.METHOD])@ExtensionAnnotation(SayOnFailExtension)public @interface SayOnFail { Retention    String value() default     String voice() default "Alex"}
    93. 93. Step 1: Create Annotation@Retention(RetentionPolicy.RUNTIME)@Target([ElementType.TYPE, ElementType.METHOD])@ExtensionAnnotation(SayOnFailExtension)public @interface SayOnFail { Target    String value() default     String voice() default "Alex"}
    94. 94. Step 1: Create Annotation@Retention(RetentionPolicy.RUNTIME)@Target([ElementType.TYPE, ElementType.METHOD])@ExtensionAnnotation(SayOnFailExtension)public @interface SayOnFail { Extension    String value() default     String voice() default "Alex"}
    95. 95. Step 1: Create Annotation@Retention(RetentionPolicy.RUNTIME)@Target([ElementType.TYPE, ElementType.METHOD])@ExtensionAnnotation(SayOnFailExtension) Default Valuespublic @interface SayOnFail {    String value() default     String voice() default "Alex"}
    96. 96. Step 2: Create Extensionclass SayOnFailExtension extends AbstractAnnotationDrivenExtension<SayOnFail>{    @Override    void visitSpecAnnotation(SayOnFail sayOnError, SpecInfo spec) {        spec.features.each {FeatureInfo feature ->            if(!feature.featureMethod.reflection.isAnnotationPresent(SayOnFail)) {                visitFeatureAnnotation(sayOnError, feature)            }        }    }    @Override    void visitFeatureAnnotation(SayOnFail sayOnError, FeatureInfo feature) {        def interceptor = new SayOnFailInterceptor(sayOnError, feature)        feature.getFeatureMethod().addInterceptor(interceptor)    }}
    97. 97. Step 2: Create Extensionclass SayOnFailExtension extends AbstractAnnotationDrivenExtension<SayOnFail>{    @Override    void visitSpecAnnotation(SayOnFail sayOnError, SpecInfo spec) {        spec.features.each {FeatureInfo feature ->            if(!feature.featureMethod.reflection.isAnnotationPresent(SayOnFail)) {                visitFeatureAnnotation(sayOnError, feature)            }        }    } Extend    @Override    void visitFeatureAnnotation(SayOnFail sayOnError, FeatureInfo feature) {        def interceptor = new SayOnFailInterceptor(sayOnError, feature)        feature.getFeatureMethod().addInterceptor(interceptor)    }}
    98. 98. Step 2: Create Extensionclass SayOnFailExtension extends AbstractAnnotationDrivenExtension<SayOnFail>{    @Override    void visitSpecAnnotation(SayOnFail sayOnError, SpecInfo spec) {        spec.features.each {FeatureInfo feature ->            if(!feature.featureMethod.reflection.isAnnotationPresent(SayOnFail)) {                visitFeatureAnnotation(sayOnError, feature)            }        }    } Override for spec    @Override    void visitFeatureAnnotation(SayOnFail sayOnError, FeatureInfo feature) {        def interceptor = new SayOnFailInterceptor(sayOnError, feature)        feature.getFeatureMethod().addInterceptor(interceptor)    }}
    99. 99. Step 2: Create Extensionclass SayOnFailExtension extends AbstractAnnotationDrivenExtension<SayOnFail>{    @Override    void visitSpecAnnotation(SayOnFail sayOnError, SpecInfo spec) {        spec.features.each {FeatureInfo feature ->            if(!feature.featureMethod.reflection.isAnnotationPresent(SayOnFail)) {                visitFeatureAnnotation(sayOnError, feature)            }        } Add interceptor to all    } methods    @Override    void visitFeatureAnnotation(SayOnFail sayOnError, FeatureInfo feature) {        def interceptor = new SayOnFailInterceptor(sayOnError, feature)        feature.getFeatureMethod().addInterceptor(interceptor)    }}
    100. 100. Step 2: Create Extensionclass SayOnFailExtension extends AbstractAnnotationDrivenExtension<SayOnFail>{    @Override    void visitSpecAnnotation(SayOnFail sayOnError, SpecInfo spec) {        spec.features.each {FeatureInfo feature ->            if(!feature.featureMethod.reflection.isAnnotationPresent(SayOnFail)) {                visitFeatureAnnotation(sayOnError, feature)            }        }    } Override for feature    @Override    void visitFeatureAnnotation(SayOnFail sayOnError, FeatureInfo feature) {        def interceptor = new SayOnFailInterceptor(sayOnError, feature)        feature.getFeatureMethod().addInterceptor(interceptor)    }}
    101. 101. Step 2: Create Extensionclass SayOnFailExtension extends AbstractAnnotationDrivenExtension<SayOnFail>{    @Override    void visitSpecAnnotation(SayOnFail sayOnError, SpecInfo spec) {        spec.features.each {FeatureInfo feature ->            if(!feature.featureMethod.reflection.isAnnotationPresent(SayOnFail)) {                visitFeatureAnnotation(sayOnError, feature)            }        }    } Create Interceptor    @Override    void visitFeatureAnnotation(SayOnFail sayOnError, FeatureInfo feature) {        def interceptor = new SayOnFailInterceptor(sayOnError, feature)        feature.getFeatureMethod().addInterceptor(interceptor)    }}
    102. 102. Step 2: Create Extensionclass SayOnFailExtension extends AbstractAnnotationDrivenExtension<SayOnFail>{    @Override    void visitSpecAnnotation(SayOnFail sayOnError, SpecInfo spec) {        spec.features.each {FeatureInfo feature ->            if(!feature.featureMethod.reflection.isAnnotationPresent(SayOnFail)) {                visitFeatureAnnotation(sayOnError, feature)            }        }    } Add to Feature    @Override    void visitFeatureAnnotation(SayOnFail sayOnError, FeatureInfo feature) {        def interceptor = new SayOnFailInterceptor(sayOnError, feature)        feature.getFeatureMethod().addInterceptor(interceptor)    }}
    103. 103. Step 3: Create Intercepterclass SayOnFailInterceptor implements IMethodInterceptor{    SayOnFail sayOnError    FeatureInfo featureInfo    SayOnFailInterceptor(SayOnFail sayOnError, FeatureInfo featureInfo) {        this.sayOnError = sayOnError        this.featureInfo = featureInfo    }    void intercept(IMethodInvocation invocation) {        try {           invocation.proceed()        } catch(Throwable t) {            def methodName = featureInfo.getFeatureMethod().name            def voiceName = sayOnError.voice()            def sayText = sayOnError.value() ?: "Danger! Failure for: $methodName"            try {                "say -v $voiceName $sayText".execute()            } catch (IOException ex) {}                        throw t        }    }}
    104. 104. Step 3: Create Intercepterclass SayOnFailInterceptor implements IMethodInterceptor{    SayOnFail sayOnError    FeatureInfo featureInfo    SayOnFailInterceptor(SayOnFail sayOnError, FeatureInfo featureInfo) {        this.sayOnError = sayOnError        this.featureInfo = featureInfo    } Implement Interface    void intercept(IMethodInvocation invocation) {        try {           invocation.proceed()        } catch(Throwable t) {            def methodName = featureInfo.getFeatureMethod().name            def voiceName = sayOnError.voice()            def sayText = sayOnError.value() ?: "Danger! Failure for: $methodName"            try {                "say -v $voiceName $sayText".execute()            } catch (IOException ex) {}                        throw t        }    }}
    105. 105. Step 3: Create Intercepterclass SayOnFailInterceptor implements IMethodInterceptor{    SayOnFail sayOnError    FeatureInfo featureInfo    SayOnFailInterceptor(SayOnFail sayOnError, FeatureInfo featureInfo) {        this.sayOnError = sayOnError        this.featureInfo = featureInfo    } Override    void intercept(IMethodInvocation invocation) {        try {           invocation.proceed()        } catch(Throwable t) {            def methodName = featureInfo.getFeatureMethod().name            def voiceName = sayOnError.voice()            def sayText = sayOnError.value() ?: "Danger! Failure for: $methodName"            try {                "say -v $voiceName $sayText".execute()            } catch (IOException ex) {}                        throw t        }    }}
    106. 106. Step 3: Create Intercepterclass SayOnFailInterceptor implements IMethodInterceptor{    SayOnFail sayOnError    FeatureInfo featureInfo    SayOnFailInterceptor(SayOnFail sayOnError, FeatureInfo featureInfo) {        this.sayOnError = sayOnError        this.featureInfo = featureInfo    } run method    void intercept(IMethodInvocation invocation) {        try {           invocation.proceed()        } catch(Throwable t) {            def methodName = featureInfo.getFeatureMethod().name            def voiceName = sayOnError.voice()            def sayText = sayOnError.value() ?: "Danger! Failure for: $methodName"            try {                "say -v $voiceName $sayText".execute()            } catch (IOException ex) {}                        throw t        }    }}
    107. 107. Step 3: Create Intercepterclass SayOnFailInterceptor implements IMethodInterceptor{    SayOnFail sayOnError    FeatureInfo featureInfo    SayOnFailInterceptor(SayOnFail sayOnError, FeatureInfo featureInfo) {        this.sayOnError = sayOnError        this.featureInfo = featureInfo    } if test fails    void intercept(IMethodInvocation invocation) {        try {           invocation.proceed()        } catch(Throwable t) {            def methodName = featureInfo.getFeatureMethod().name            def voiceName = sayOnError.voice()            def sayText = sayOnError.value() ?: "Danger! Failure for: $methodName"            try {                "say -v $voiceName $sayText".execute()            } catch (IOException ex) {}                        throw t        }    }}
    108. 108. Step 3: Create Intercepterclass SayOnFailInterceptor implements IMethodInterceptor{    SayOnFail sayOnError    FeatureInfo featureInfo    SayOnFailInterceptor(SayOnFail sayOnError, FeatureInfo featureInfo) {        this.sayOnError = sayOnError        this.featureInfo = featureInfo    } do your thing    void intercept(IMethodInvocation invocation) {        try {           invocation.proceed()        } catch(Throwable t) {            def methodName = featureInfo.getFeatureMethod().name            def voiceName = sayOnError.voice()            def sayText = sayOnError.value() ?: "Danger! Failure for: $methodName"            try {                "say -v $voiceName $sayText".execute()            } catch (IOException ex) {}                        throw t        }    }}
    109. 109. Step 3: Create Intercepterclass SayOnFailInterceptor implements IMethodInterceptor{    SayOnFail sayOnError    FeatureInfo featureInfo    SayOnFailInterceptor(SayOnFail sayOnError, FeatureInfo featureInfo) {        this.sayOnError = sayOnError        this.featureInfo = featureInfo    } Throw Execption!    void intercept(IMethodInvocation invocation) {        try {           invocation.proceed()        } catch(Throwable t) {            def methodName = featureInfo.getFeatureMethod().name            def voiceName = sayOnError.voice()            def sayText = sayOnError.value() ?: "Danger! Failure for: $methodName"            try {                "say -v $voiceName $sayText".execute()            } catch (IOException ex) {}                        throw t        }    }}
    110. 110. @Documentclass LibraryControllerSpec extends Specification{    @Author(Zan)    @JiraIssue(MBL-42)    void "successfully return book to library"() {        given: "there is a checked out book and a active library"            Book book = new Book(isdn: 1234567ABC).save(validate: false)            Library library = new Library(name: Carnegie).save()        and:"the controller has all dependancy"            CatalogService catalogService = Mock()            controller.catalogService = catalogService        and: "submit library id and isdn"            params.id = library.id            params.isdn = book.isdn        when: "submit the form"            controller.returnBook()        and: "get the JSON from the response"            JSONElement json = response.getJson()        then: "verify the returned book is FILED"            json.isdn == book.isdn            json.status == FILE    }}
    111. 111. @Retention(RetentionPolicy.RUNTIME)@Target([ElementType.TYPE])@ExtensionAnnotation(DocumentExtension)public @interface Document {}
    112. 112. class DocumentExtension extends AbstractAnnotationDrivenExtension<Document> {    @Override    void visitSpecAnnotation(Document document, SpecInfo specInfo) {        specInfo.addListener(new DocumentListener(document))    }}
    113. 113. class DocumentListener extends AbstractRunListener{    private Document document    File baseDir = new File("src/docs/ref/Tests")    File output    DocumentListener(Document document) {        this.document = document        baseDir.mkdirs()    }    void beforeSpec(SpecInfo specInfo) {...}        void beforeFeature(FeatureInfo featureInfo) {...}    void beforeIteration(IterationInfo iterationInfo) {...}    void afterIteration(IterationInfo iterationInfo) {...}    void afterFeature(FeatureInfo featureInfo) {...}    void error(ErrorInfo errorInfo) {...}    void featureSkipped(FeatureInfo featureInfo) {...}    void afterSpec(SpecInfo specInfo) {...}        void specSkipped(SpecInfo specInfo) {...}}
    114. 114. Core Spock Links http://meetspock.appspot.com/ http://code.google.com/p/spock/wiki https://github.com/spockframework/spockInsightful Posts http://kousenit.wordpress.com/2011/08/20/i-think-i-get-spock-mocks-now/ http://hamletdarcy.blogspot.com/2009/05/peek-inside-spock-framework.htmlExtensions https://github.com/zanthrash/spock-extensions https://github.com/robfletcher/spock-extensions

    ×