Search code examples
javavalidationfiltergroovyspock

How to merge two test cases with Groovy Spock where a void type of method once succeeds and once throws exception


I have the following 2 test cases for the same method.

The first calls a service where there is no result (because the service method is a void).

The second calls the same service but then it results with an exception.

How can I merge these two test cases into one by using the @Unroll and where parts?

Test 1:

def "test is valid by case - valid"() {
    given: "data to validate"
    Data data = Mock()
    
    when: "trying to check if valid"
    def result = validationService.isValidByCriteria(data)

    then: "no exception is thrown"
    noExceptionThrown()
    and: "validation is called without exception"
    1 * validationLogic.validate(data)
    and: "no exception therefore true returns"
    result
}

Test 2:

def "test is valid by case - invalid"() {
    given: "data to validate"
    Data data = Mock()
    
    when: "trying to check if valid"
    def result = validationService.isValidByCriteria(data)

    then: "no exception is thrown"
    noExceptionThrown()
    and: "validation is called with exception"
    1 * validationLogic.validate(data) >> {throw new Exception()}
    and: "exception therefore false returns"
    !result
}

Solution

  • I am not sure you should bake those two tests into a single one, because they represent different feature behaviours. I understand your motivation to reduce duplicate code, though.

    I guess your situation is roughly as follows:

    class ValidationService {
      ValidationLogic validationLogic
    
      boolean isValidByCriteria(Data data) {
        try {
          validationLogic.validate(data)
        }
        catch (Exception e) {
          println "Validation error: $e.message"
          return false
        }
        return true
      }
    }
    
    class ValidationLogic {
      void validate(Data data) {
        if (data.name.contains('x'))
          throw new Exception('invalid data')
      }
    }
    
    class Data {
      String name
    }
    

    Then your original test runs as given. One way to unify those two methods looks like this:

    class ValidationServiceTest extends Specification {
      def validationLogic = Mock(ValidationLogic)
      def validationService = new ValidationService(validationLogic: validationLogic)
    
      @Unroll('#resultType result')
      def 'validation service returns normally'() {
        given: 'data to validate'
        Data data = Mock()
    
        when: 'trying to check if valid'
        def result = validationService.isValidByCriteria(data)
    
        then: 'service does not throw any exceptions'
        noExceptionThrown()
        and: 'validation is called'
        1 * validationLogic.validate(data) >> { if (!expectedResult) throw new Exception('oops') }
        and: 'result is as expected'
        result == expectedResult
    
        where:
        resultType | expectedResult
        'valid'    | true
        'invalid'  | false
      }
    }
    

    In IntelliJ IDEA the unrolled feature is represented like this:

    IDEA test results

    Try it in the Groovy Web Console.