SlideShare a Scribd company logo
1 of 118
Spock
Zan Thrash
@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'
@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'
@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'
@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'
}
@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'
}
@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'
       }
}
@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'
       }
}
@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' )
       }
}
@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' )
       }
}
Fixture Methods
def setup() {}
def cleanup() {}
def setupSpec() {}
def cleanupSpec() {}
void "add 2 things together"() {
    expect:
        1 + 2 == 3
           'Bat' + 'man' == 'Bat man'
        99 + 1 == 100
}
Condition not satisfied:

'Bat' + 'man' == 'Bat man'
      |       |
      Batman  false
              1 difference (85% similarity)
              Bat(-)man
              Bat( )man
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
void "add 2 things together"() {
    expect:
        1 + 2 == 3
           'Bat' + 'man' == 'Bat man'
        99 + 1 == 100
}
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']
}
@Unroll
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']
}
@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']
}
@Unroll
void "#a + #b = #c"() {
    expect:"a + b = c"
        a + b == c
    where:
        a << [1, 'Bat', 99]
        b << [1, 'man', 1]
        c << [2, 'Batman', '100']
}
@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"

}
void "custom data provider" () {
    expect:"a + b = c"
        a + b == c

    where:
        [a, b, c] << new ABCDataProvider()
}
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() { }
}
Interactions
def catalogService = Mock(CatalogService)


CatalogService catalogService = Mock()
def catalogService = Mock(CatalogService)


CatalogService catalogService = Mock()
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
}
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
}
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
}
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
}
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
}
1 * catalogService.file(book, library) >> 'FILED'


  Carnality
1 * catalogService.file(book, library) >> 'FILED'


  Carnality
1 * catalogService.file(book, library) >> 'FILED'


  Carnality
(0..3) * catalogService.file(book, library) >> 'FILED'


        Carnality
(3.._) * catalogService.file(book, library) >> 'FILED'


        Carnality
(_..3) * catalogService.file(book, library) >> 'FILED'


        Carnality
TooFewInvocationsError
TooManyInvocationsError
(_..3) * catalogService.file(book, library) >> 'FILED'


        Carnality Target Constraint
(_..3) * catalogService.file(book, library) >> 'FILED'


        Carnality Target Constraint
(_..3) * _.file(book, library) >> 'FILED'


             Target Constraint
(_..3) * _.file(book, library) >> 'FILED'


             Target Constraint
(_..3) * catalogService.file(book, library) >> 'FILED'


                   Target Constraint Conststraint
                              Method
(_..3) * catalogService.file(book, library) >> 'FILED'


                   Target Constraint Conststraint
                              Method
(_..3) * catalogService./f.*/(book, library) >> 'FILED'


                                Method Conststraint
(_..3) * catalogService./f.*/(book, library) >> 'FILED'


                                Method Conststraint
(_..3) * catalogService._(book, library) >> 'FILED'


                               Method Conststraint
(_..3) * catalogService._(book, library) >> 'FILED'


                               Method Conststraint
(_..3) * catalogService.file(book, library) >> 'FILED'


                               Method Conststraint List Constraint
                                         Argument
(_..3) * catalogService.file(book, library) >> 'FILED'


                               Method Conststraint List Constraint
                                         Argument
(_..3) * catalogService.file(_, _) >> 'FILED'


                                         Argument List Constraint
(_..3) * catalogService.file(*_) >> 'FILED'


                                      Argument List Constraint
(_..3) * catalogService.file(_ as Book, _) >> 'FILED'


                                           Argument List Constraint
(_..3) * catalogService.file(_ as Book, !null) >> 'FILED'


                                           Argument List Constraint
(_..3) * catalogService.file({it.isdn > 1}, !null) >> 'FILED'


                                            Argument List Constraint
1 * catalogService.file(book, library) >> 'FILED'


                                    Return Values Constraint
                                    Argument List
1 * catalogService.file(book, library) >> 'FILED'


                                    Return Values Constraint
                                    Argument List
3 * catalogService.file(book, library) >>> ['FILED',‘ERROR’]


                                    Return Values
3 * catalogService.file(book, library) >> {
                                  book.status = FILED
                                  return book
                                 }



                                   Return Values
3 * catalogService.file(book, library) >> {
                        throw new TimeoutException()
                     }



                                   Return Values
foo.bar() >> { throw new IOException() } >>> [1, 2, 3] >> { throw new RuntimeException() } 




                                                  Return Values
(_.._) * _._(*_)
Order Is NOT enforced

 void "test ordered interactions"() {
   ...

       then:"verify the book was filed"
           1 * service.inquired(_)
           2 * service.notifyLibrarian(*_)
           result == true
 }
Interactions are NOT ordered

void "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
}
Extensions
@Unroll
@Shared
@Timeout()
     @Timeout(10)
@Timeout(value=10, unit=TimeUnit.MILLISECONDS)
@Stepwise
@Ignore
@IgnoreRest
@IgnoreIf({ 1 })
@FailsWith(IOException)
@AutoCleanup()
@AutoCleanup(‘dispose’)
@AutoCleanup(quite=true)
@Use([Category])
class Dynamic {}
void "test the Use annotation"() {
    when:"call awesomeness"
        def result = new Dynamic().awesomeness('very')
    then:"check result"
        result == 'so very awesome'

}
class DynamicCategory {
    
    static String awesomeness(Dynamic dynamic, String foo) {
        “so $foo awesome”
    }
    
}
@Use(DynamicCategory)
void "test the Use annotation"() {
    when:"call awesomeness"
        def result = new Dynamic().awesomeness('very')
    then:"check result"
        result == 'so very awesome'

}
@Use(DynamicCategory)
void "test the Use annotation"() {
    when:"call awesomeness"
        def result = new Dynamic().awesomeness('very')
    then:"check result"
        result == 'so very awesome'

}
void "test the Use annotation"() {
    when:"call awesomeness"
             use(DynamicCategory){
            def result = new Dynamic().awesomeness('very')
             }
    then:"check result"
        result == 'so very awesome'

}
@ConfineMetaClassChanges([Class])
~/.spock/SpockConfig.groovy
import spock.util.mop.Use
import spock.lang.*
import grails.plugin.spock.IntegrationSpec

runner {
    optimizeRunOrder true
    filterStackTrace false

    include Unroll, Use

    exclude {
        baseClass IntegrationSpec
    }

}
>$ grails -Dspock.configuration=test/SpockConfig.groovy test-app
Rolling your own
Spock Extensions
import spock.lang.*

@SayOnFail
class MyFirstSpec extends Specification {

  @SayOnFail(value="Danger! Danger!", voice='Zarvox')
  def "let's try this!"() {
    expect:
    Math.max(1, 2) == 3
  }
}
Step 1: Create Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.TYPE, ElementType.METHOD])
@ExtensionAnnotation(SayOnFailExtension)

public @interface SayOnFail {
    String value() default ''
    String voice() default "Alex"
}
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"
}
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"
}
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"
}
Step 1: Create Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.TYPE, ElementType.METHOD])
@ExtensionAnnotation(SayOnFailExtension)
                                    Default Values
public @interface SayOnFail {
    String value() default ''
    String voice() default "Alex"
}
Step 2: Create Extension
class 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)
      }
}
Step 2: Create Extension
class 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)
      }
}
Step 2: Create Extension
class 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)
      }
}
Step 2: Create Extension
class 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)
      }
}
Step 2: Create Extension
class 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)
      }
}
Step 2: Create Extension
class 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)
      }
}
Step 2: Create Extension
class 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)
      }
}
Step 3: Create Intercepter
class 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
          }
      }
}
Step 3: Create Intercepter
class 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
          }
      }
}
Step 3: Create Intercepter
class 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
          }
      }
}
Step 3: Create Intercepter
class 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
          }
      }
}
Step 3: Create Intercepter
class 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
          }
      }
}
Step 3: Create Intercepter
class 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
          }
      }
}
Step 3: Create Intercepter
class 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
          }
      }
}
@Document
class 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'
      }
}
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.TYPE])
@ExtensionAnnotation(DocumentExtension)

public @interface Document {}
class DocumentExtension extends AbstractAnnotationDrivenExtension<Document> {
    @Override
    void visitSpecAnnotation(Document document, SpecInfo specInfo) {
        specInfo.addListener(new DocumentListener(document))
    }

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

}
Core Spock Links
 http://meetspock.appspot.com/

 http://code.google.com/p/spock/wiki

 https://github.com/spockframework/spock


Insightful 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.html

Extensions
 https://github.com/zanthrash/spock-extensions

 https://github.com/robfletcher/spock-extensions

More Related Content

Recently uploaded

Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 

Recently uploaded (20)

Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 

Featured

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by HubspotMarius Sescu
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTExpeed Software
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsPixeldarts
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthThinkNow
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfmarketingartwork
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 

Featured (20)

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPT
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage Engineerings
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental Health
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
 
Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 

GR8 Conf US: Spock Soup to Nuts

  • 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. @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. @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. @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. @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. @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. @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. @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. @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' ) } }
  • 17. void "add 2 things together"() {     expect:         1 + 2 == 3 'Bat' + 'man' == 'Bat man'         99 + 1 == 100 }
  • 18. Condition not satisfied: 'Bat' + 'man' == 'Bat man'       |       |       Batman  false               1 difference (85% similarity)               Bat(-)man               Bat( )man
  • 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. void "add 2 things together"() {     expect:         1 + 2 == 3 'Bat' + 'man' == 'Bat man'         99 + 1 == 100 }
  • 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.
  • 23. @Unroll 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.
  • 25. @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'] }
  • 26.
  • 27. @Unroll void "#a + #b = #c"() {     expect:"a + b = c"         a + b == c     where:         a << [1, 'Bat', 99]         b << [1, 'man', 1]         c << [2, 'Batman', '100'] }
  • 28. @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" }
  • 29. void "custom data provider" () {     expect:"a + b = c"         a + b == c     where:         [a, b, c] << new ABCDataProvider() }
  • 30. 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() { } }
  • 32. def catalogService = Mock(CatalogService) CatalogService catalogService = Mock()
  • 33. def catalogService = Mock(CatalogService) CatalogService catalogService = Mock()
  • 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"         params.isdn = book.isdn         def result = controller.verifyISDN()     then:"verify the book was filed"         1 * service.inquired(_)         result == true }
  • 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" Global         params.isdn = book.isdn         def result = controller.verifyISDN()     then:"verify the book was filed"         1 * service.inquired(_)         result == true }
  • 36. 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 }
  • 37. 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 }
  • 38. 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 }
  • 39. 1 * catalogService.file(book, library) >> 'FILED' Carnality
  • 40. 1 * catalogService.file(book, library) >> 'FILED' Carnality
  • 41. 1 * catalogService.file(book, library) >> 'FILED' Carnality
  • 42. (0..3) * catalogService.file(book, library) >> 'FILED' Carnality
  • 43. (3.._) * catalogService.file(book, library) >> 'FILED' Carnality
  • 44. (_..3) * catalogService.file(book, library) >> 'FILED' Carnality
  • 46. (_..3) * catalogService.file(book, library) >> 'FILED' Carnality Target Constraint
  • 47. (_..3) * catalogService.file(book, library) >> 'FILED' Carnality Target Constraint
  • 48. (_..3) * _.file(book, library) >> 'FILED' Target Constraint
  • 49. (_..3) * _.file(book, library) >> 'FILED' Target Constraint
  • 50. (_..3) * catalogService.file(book, library) >> 'FILED' Target Constraint Conststraint Method
  • 51. (_..3) * catalogService.file(book, library) >> 'FILED' Target Constraint Conststraint Method
  • 52. (_..3) * catalogService./f.*/(book, library) >> 'FILED' Method Conststraint
  • 53. (_..3) * catalogService./f.*/(book, library) >> 'FILED' Method Conststraint
  • 54. (_..3) * catalogService._(book, library) >> 'FILED' Method Conststraint
  • 55. (_..3) * catalogService._(book, library) >> 'FILED' Method Conststraint
  • 56. (_..3) * catalogService.file(book, library) >> 'FILED' Method Conststraint List Constraint Argument
  • 57. (_..3) * catalogService.file(book, library) >> 'FILED' Method Conststraint List Constraint Argument
  • 58. (_..3) * catalogService.file(_, _) >> 'FILED' Argument List Constraint
  • 59. (_..3) * catalogService.file(*_) >> 'FILED' Argument List Constraint
  • 60. (_..3) * catalogService.file(_ as Book, _) >> 'FILED' Argument List Constraint
  • 61. (_..3) * catalogService.file(_ as Book, !null) >> 'FILED' Argument List Constraint
  • 62. (_..3) * catalogService.file({it.isdn > 1}, !null) >> 'FILED' Argument List Constraint
  • 63. 1 * catalogService.file(book, library) >> 'FILED' Return Values Constraint Argument List
  • 64. 1 * catalogService.file(book, library) >> 'FILED' Return Values Constraint Argument List
  • 65. 3 * catalogService.file(book, library) >>> ['FILED',‘ERROR’] Return Values
  • 66. 3 * catalogService.file(book, library) >> { book.status = FILED return book } Return Values
  • 67. 3 * catalogService.file(book, library) >> { throw new TimeoutException() } Return Values
  • 68. foo.bar() >> { throw new IOException() } >>> [1, 2, 3] >> { throw new RuntimeException() }  Return Values
  • 70. Order Is NOT enforced void "test ordered interactions"() {   ...     then:"verify the book was filed"         1 * service.inquired(_)         2 * service.notifyLibrarian(*_)         result == true }
  • 71. Interactions are NOT ordered void "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 }
  • 75. @Timeout() @Timeout(10) @Timeout(value=10, unit=TimeUnit.MILLISECONDS)
  • 84. void "test the Use annotation"() {     when:"call awesomeness"         def result = new Dynamic().awesomeness('very')     then:"check result"         result == 'so very awesome' }
  • 85. class DynamicCategory {          static String awesomeness(Dynamic dynamic, String foo) {         “so $foo awesome”     }      }
  • 86. @Use(DynamicCategory) void "test the Use annotation"() {     when:"call awesomeness"         def result = new Dynamic().awesomeness('very')     then:"check result"         result == 'so very awesome' }
  • 87. @Use(DynamicCategory) void "test the Use annotation"() {     when:"call awesomeness"         def result = new Dynamic().awesomeness('very')     then:"check result"         result == 'so very awesome' }
  • 88. void "test the Use annotation"() {     when:"call awesomeness" use(DynamicCategory){         def result = new Dynamic().awesomeness('very') }     then:"check result"         result == 'so very awesome' }
  • 90. ~/.spock/SpockConfig.groovy import spock.util.mop.Use import spock.lang.* import grails.plugin.spock.IntegrationSpec runner {     optimizeRunOrder true     filterStackTrace false     include Unroll, Use     exclude {         baseClass IntegrationSpec     } }
  • 93. import spock.lang.* @SayOnFail class MyFirstSpec extends Specification {   @SayOnFail(value="Danger! Danger!", voice='Zarvox')   def "let's try this!"() {     expect:     Math.max(1, 2) == 3   } }
  • 94. Step 1: Create Annotation @Retention(RetentionPolicy.RUNTIME) @Target([ElementType.TYPE, ElementType.METHOD]) @ExtensionAnnotation(SayOnFailExtension) public @interface SayOnFail {     String value() default ''     String voice() default "Alex" }
  • 95. 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" }
  • 96. 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" }
  • 97. 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" }
  • 98. Step 1: Create Annotation @Retention(RetentionPolicy.RUNTIME) @Target([ElementType.TYPE, ElementType.METHOD]) @ExtensionAnnotation(SayOnFailExtension) Default Values public @interface SayOnFail {     String value() default ''     String voice() default "Alex" }
  • 99. Step 2: Create Extension class 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)     } }
  • 100. Step 2: Create Extension class 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)     } }
  • 101. Step 2: Create Extension class 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)     } }
  • 102. Step 2: Create Extension class 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)     } }
  • 103. Step 2: Create Extension class 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)     } }
  • 104. Step 2: Create Extension class 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)     } }
  • 105. Step 2: Create Extension class 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)     } }
  • 106. Step 3: Create Intercepter class 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         }     } }
  • 107. Step 3: Create Intercepter class 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         }     } }
  • 108. Step 3: Create Intercepter class 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         }     } }
  • 109. Step 3: Create Intercepter class 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         }     } }
  • 110. Step 3: Create Intercepter class 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         }     } }
  • 111. Step 3: Create Intercepter class 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         }     } }
  • 112. Step 3: Create Intercepter class 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         }     } }
  • 113. @Document class 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'     } }
  • 115. class DocumentExtension extends AbstractAnnotationDrivenExtension<Document> {     @Override     void visitSpecAnnotation(Document document, SpecInfo specInfo) {         specInfo.addListener(new DocumentListener(document))     } }
  • 116. 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) {...} }
  • 117.
  • 118. Core Spock Links http://meetspock.appspot.com/ http://code.google.com/p/spock/wiki https://github.com/spockframework/spock Insightful 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.html Extensions https://github.com/zanthrash/spock-extensions https://github.com/robfletcher/spock-extensions

Editor's Notes

  1. - 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
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n
  45. \n
  46. \n
  47. \n
  48. \n
  49. \n
  50. \n
  51. \n
  52. \n
  53. \n
  54. \n
  55. \n
  56. \n
  57. \n
  58. \n
  59. \n
  60. - runs before every feature method\n- @Before (JUnit 4)\n- void setup() (JUnit 3)\n\n\n
  61. - runs after every feature method\n- @After (JUnit 4)\n- void tearDown() (JUnit 3)\n
  62. - runs before the first feature method \n- @BeforeClass (JUnit 4)\n
  63. - runs after the last feature method\n- @AfterClass (JUnit 4)\n
  64. - Another structure you can use to test is the &amp;#x2018;expect&amp;#x2019; block\n- &amp;#x2018;When&amp;#x2019; and &amp;#x2018;Then&amp;#x2019; block rolled up into one\n- Best for testing functional behavior that has no side effects\n\n\n
  65. \n
  66. 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
  67. Problems with this:\n\n- Code and data are mixed together and can&amp;#x2019;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
  68. \n\nUsing the &amp;#x2018;where&amp;#x2019; block and Data Pipes\n\nIntroduce Where Block &amp; 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
  69. - 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
  70. Plain @Unroll gives us individual test output, w/ an zero base ordinal position of the test\n
  71. \n
  72. - @Unroll with a value gives us individual test output with as much details of the test and paramaters as we want.\n\n
  73. \n
  74. - 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
  75. - 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
  76. - multiple databinding\n- can be used to get data from db, excel file; or to randomally generate data for test\n
  77. - hasNext()\n- next()\n- size()\n- close() is called but can be a no opt if not needed, useful for db\n
  78. - 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
  79. Two way to create a mock\n\nMock() is a method on the Specification superclass\n\ndynamic\n\nstatic\n\n
  80. Two way to create a mock\n\nMock() is a method on the Specification superclass\n\ndynamic\n\nstatic\n\n
  81. Global vs. Local Scoping\n\nOutside of &amp;#x2018;when&amp;#x2019; block = global = valid from point of definition to end of feature method\n\nInside of &amp;#x2018;then&amp;#x2019; block = local = valid only from the preceding &amp;#x2018;when&amp;#x2019; block\n\n
  82. Global vs. Local Scoping\n\nOutside of &amp;#x2018;when&amp;#x2019; block = global = valid from point of definition to end of feature method\n\nInside of &amp;#x2018;then&amp;#x2019; block = local = valid only from the preceding &amp;#x2018;when&amp;#x2019; block\n\n
  83. Global vs. Local Scoping\n\nOutside of &amp;#x2018;when&amp;#x2019; block = global = valid from point of definition to end of feature method\n\nInside of &amp;#x2018;then&amp;#x2019; block = local = valid only from the preceding &amp;#x2018;when&amp;#x2019; block\n\nDon&amp;#x2019;t mix the two up. Local will overwrite global and can cause some confusion in your expected output.\n\nKen Kousin \n\n
  84. 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
  85. 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
  86. Ananomy of a interaction\n
  87. Ananomy of a interaction\n
  88. Cardnality\n\nexactly &amp;#x2018;n&amp;#x2019; number of times\n\n
  89. Cardnality\n\nexplicit range\n\n
  90. Cardnality\n\nat least 3 times with an wildcarded upper bound\n\n
  91. Cardnality\n\nat most 3 times with an wildcarded lower bound\n\n
  92. InteractionNotSatisfiedError\n
  93. Target Constraint\n\nCan be for a specific mock instance\n\n
  94. Target Constraint\n\nCan be for a specific mock instance\n\n
  95. Target Constraint\n\nCan be for a specific mock instance\n\n
  96. Target Constraint\n\nCan be for a specific mock instance\n\n
  97. Target Constraint\n\nor a wild carded (all mocks)\n
  98. Target Constraint\n\nor a wild carded (all mocks)\n
  99. Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  100. Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  101. Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  102. Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  103. Method Constraints\n\nRegEx.\n\n
  104. Method Constraints\n\nRegEx.\n\n
  105. Method Constraints\n\nCan be wild carded for a call to that method signature on any mock object\n\n
  106. Method Constraints\n\nCan be wild carded for a call to that method signature on any mock object\n\n
  107. Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  108. Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  109. Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  110. Method Constraints\n\nExplicit: Define the method interaction to be called\n\n
  111. Method Constraints\n\nwildcards: Any 2 arguments\n\n
  112. Method Constraints\n\n*_ matches any argument list\n\n
  113. Method Constraints\n\nAny param that is an instance of a Class\n\n\n\n
  114. Method Constraints\n\nSecond param is any non-null value\n\n\n\n
  115. Method Constraints\n\ncustom param as a closure\n\n\n
  116. 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
  117. 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
  118. 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
  119. 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
  120. 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
  121. Return Values: Custom\n\n\n\n\n\n
  122. Return Values: Custom Action\n\nStub out the throwing of an error\n\n\n\n\n\n
  123. Return Values: Chained\n\nInteractions are NOT ordered\n\nIf declared in the same &amp;#x2018;then&amp;#x2019; 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
  124. Return Values: Chained\n\nInteractions are NOT ordered\n\nIf declared in the same &amp;#x2018;then&amp;#x2019; 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
  125. If declared in the same &amp;#x2018;then&amp;#x2019; block the interactions enforcement of order dose not exist\n\nCan override this with multiple THEN blocks and putting interactions within\n
  126. If declared in the same &amp;#x2018;then&amp;#x2019; 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
  127. Extensions are m\n
  128. Type and Method annotation\n\nUnrolls the paramertized test and replaces the test name as\n
  129. 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
  130. 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
  131. 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
  132. Type &amp; Method annotation\n\nIndicates the method should not be run\n\n
  133. 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
  134. Method &amp; Class annotation\n\nTakes a closure that should return something that resolves to a truthy Groovy statement.\n\n\n\n\n
  135. Method &amp; Class annotation\n\nTakes a Throwable class that the SUT is expected to throw\n\n\n
  136. Field annotation\n\nBy default calles the &amp;#x2018;close&amp;#x2019; 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
  137. Type &amp; Method annotation\n\nActivate one or more groovy Categories while the class or feature method executes\n\n\n\n
  138. 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
  139. \n\n\n\n
  140. \n\n\n\n
  141. \n\n\n\n
  142. 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
  143. 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
  144. \n
  145. \n
  146. 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
  147. \n
  148. \n
  149. \n
  150. \n
  151. \n
  152. \n
  153. \n
  154. \n
  155. \n
  156. \n
  157. \n
  158. \n
  159. \n
  160. \n
  161. \n
  162. \n
  163. \n
  164. \n
  165. \n
  166. \n
  167. Using a runtime listener\n
  168. \n
  169. \n
  170. \n
  171. \n
  172. \n