Search code examples
unit-testingspock

Set-up/clean-up only once per feature method with 'where:' block


Spock has setupSpec on the Spec class level. I would want to have something similar for a single test case level.

This might not be available in Spock, Does someone has a workaround for this.

void "test something"() {
    setup:
    User user = createUser()

    when:
    user.setAdress(new Address(zipCode: inputZipCode, city: inputCity))

    then:
    user.address.city == inputCity
    user.address.zipCode == inputZipCode

    cleanup:
    deleteUser(user)

    where:
    inputCity | inputZipCode
    "a" |"1"
    "b" |"2"
}

Creating and deleting user is unnecessarily done after every iteration.

  • Could it be possible to have something la- setupSpec for a single test instead of class-level?

  • It is possible to manipulate the test cases to use class-setupSpec/CleanupSpec or even create a new test (with @Stepwise) to achieve this but I am looking for something good solution not a hack.


Solution

  • I think this is very ugly because it involves manual bookkeeping. I do not recommend you to do it like this, but anyway:

    package de.scrum_master.stackoverflow.q57721328
    
    import spock.lang.See
    import spock.lang.Shared
    import spock.lang.Specification
    import spock.lang.Unroll
    
    class OneTimeSetupCleanupParametrisedTest extends Specification {
      @Shared User user
      @Shared int iteration
    
      User createUser() {
        // Set up test fixture only if iteration == 0 (or optionally, if fixture is uninitialised) 
        user ?: new User()
      }
    
      void deleteUser(User userInstance) {
        // Clean up counter (and test fixture, if any) only if iteration == total number of iterations
        if (++iteration == specificationContext.currentIteration.estimatedNumIterations) {
          userInstance.delete()
          user = null
          iteration = 0
        }
      }
    
    //  @Unroll
      void "test something"() {
        setup:
        // Call initialiser helper for each iteration, relying on the fact that it will only
        // create a text fixture if none exists yet
        user = createUser()
    
        when:
        user.setAdress(new Address(zipCode: inputZipCode, city: inputCity))
    
        then:
        user.address.city == inputCity
        user.address.zipCode == inputZipCode
    
        cleanup:
        // Call clean-up helper for each iteration, relying on the fact that it will only
        // clean up the fixture during the last iteration
        deleteUser(user)
    
        where:
        inputCity | inputZipCode
        "a"       | "1"
        "b"       | "2"
      }
    
      static class User {
        Address address
    
        User() {
          println "creating user"
        }
    
        void setAdress(Address address) {
          this.address = address
        }
    
        void delete() {
          println "deleting user"
          address = null
        }
      }
    
      static class Address {
        String zipCode, city
      }
    }
    

    Console log:

    creating user
    deleting user
    

    Update: The Spock manual says about this topic:

    Sharing of Objects between Iterations

    In order to share an object between iterations, it has to be kept in a @Shared or static field.

    NOTE: Only @Shared and static variables can be accessed from within a where: block.

    Note that such objects will also be shared with other methods. There is currently no good way to share an object just between iterations of the same method. If you consider this a problem, consider putting each method into a separate spec, all of which can be kept in the same file. This achieves better isolation at the cost of some boilerplate code.