8

I have a method like below in my class that I'm trying to test:

class SomeHelper { 
    ByteArrayOutputStream fooBar (Request request) {
       ByteArrayOutputStream baos = someParser.parseData(getRequestFileInputStream(request.filename))
       return baos
    }
    InputStream getRequestFileInputStream(String filename) {
      //return intputStream of object from S3
    }
....
}

In the above, getRequestFileInputStream is a method that takes as parameter a name of the file. It fetches the inputstream of that file from AWS S3. While testing fooBar method from Spock, I would like to provide a mock for the getRequestFileInputStream method because I don't want to use the implementation of this method that is in the class since it goes to another bucket name.

Is it possible to do this?

Below is what I've tried:

class SomeHelperSpec extends Specification{
    //this is the implementation of getRequestFileInputStream I want to use while testing
    InputStream getObjectFromS3(String objectName) {
            def env = System.getenv()
            AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration(env["endpoint_url"], env["region_name"])
            AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard()
            builder.setEndpointConfiguration(endpoint)
            builder.setCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(env["ACCESS_KEY"], env["SECRET_KEY"])))
            AmazonS3 s3 = builder.build()
            return s3.getObject("testbucket", objectName).getObjectContent()

    }

    def "test fooBar" () {
      given: 
         someHelper = new SomeHelper()
         someHelper.getRequestFileInputStream(_) >> getObjectFromS3(fileName)
         someHelper.someParser = Mock(SomeParser) {
           ....
         }
         Request requestInstance = new Request()
         request.filename = fileName
         request.fileType = fileType
      expect:
         someHelper.fooBar(requestInstance).getText == returnVal

      where:
         fileType | fileName      | returnVal    
         "PDF"    | "somepdf.pdf" | "somereturnval"
    }
}

However, the above doesn't work because it is still trying to call the original implementation of getRequestFileInputStream in SomeHelper instead of using the mocked implementation provided in the spec.

Anthony
  • 33,838
  • 42
  • 169
  • 278

3 Answers3

14

You can use a spy

def someHelper = Spy(SomeHelper)
someHelper.getRequestFileInputStream(_) >> getObjectFromS3(fileName)

See Spies.

Vitalii Vitrenko
  • 9,763
  • 4
  • 43
  • 62
12

You can use a real object but with overridden method:

given:
    someHelper = new SomeHelper() {
         @Override
         InputStream getRequestFileInputStream(String filename) {
             return getObjectFromS3(fileName)
         }
    }
Dmytro Maslenko
  • 2,247
  • 9
  • 16
  • As a note, you can combine both this approach and the accepted answer. You could provide a new impl of the object, then wrap it in the spy method. This is useful if you need to implement a generic interface & get the type info right while still doing interaction-based testing. For example: ` given: def mock = Spy(new MyParameterizedClass() { List someMethod() { [] } }) //... then: 1 * mock.someMethod ` – jonnybot Jun 29 '23 at 19:38
0

As a note to anyone else who finds their way here, you can combine both the above answers. You could provide a new implementation of an interface, then wrap it in a Spy. This is useful if you need to implement a generic interface & get the type info right (such as if your code is using reflection magic) while still doing interaction-based testing.

For example:

given:
def mock = Spy(new MyParameterizedClass<List>() { 
    List someMethod() { 
        return [] 
    } 
})

//...do something that makes your mock get called

expect:
1 * mock.someMethod
jonnybot
  • 2,435
  • 1
  • 32
  • 56