Skip to content

Commit

Permalink
Java Testing example using Spock (#287)
Browse files Browse the repository at this point in the history
* feat: spock testing example

* docs: update description in metadata

* docs: update example to spock

* docs: update mock title

* feat: move groovy tests to original project

* move groovy tests to original project

* chore: cleanup old project

* docs: upgrade README.md description of spock

* chore: update meta data
mridehalgh authored Nov 8, 2023
1 parent 7e5f96f commit 3c21de2
Showing 7 changed files with 184 additions and 6 deletions.
36 changes: 36 additions & 0 deletions java-test-samples/apigw-lambda-list-s3-buckets/README.md
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ This project contains automated test code samples for serverless applications. T
- [Use the SAM API Gateway emulator](#use-the-sam-api-gateway-emulator)
- [Run a unit test using a mock framework](#run-a-unit-test-using-a-mock-framework)
- [Run an integration test against cloud resources](#run-integration-tests-against-cloud-resources)
- [Testing with the Spock testing framework](#testing-with-the-spock-testing-framework)
- [Unit testing using a mock](#unit-testing-using-a-mock)
- [Invoke a Lambda function in the cloud](#invoke-a-lambda-function-in-the-cloud)
- [Fetch, tail, and filter Lambda function logs locally](#fetch-tail-and-filter-lambda-function-logs-locally)
- [Use SAM Accelerate to speed up feedback cycles](#use-sam-accelerate-to-speed-up-feedback-cycles)
@@ -172,6 +174,40 @@ public class AppWithMockTest {
}
}
```
[[top]](#api-gateway-to-lambda-to-list-s3-buckets)

## Testing with the Spock testing framework

Spock is a Groovy-based testing framework that uses a narrative style with blocks like "when", "then", and "expect" for writing clear and readable tests. It provides mocking, stubbing, and spying to isolate code under test. With its expressive Groovy syntax and declarative style Spock reduces boilerplate code and aims to simplify testing.

### Unit testing using a mock
You can use Spock to mock the service calls that are being done in the Lambda function.
[`AppWithMockSpec.groovy`](./src/test/groovy/com/example/AppWithMockSpec.groovy) covers this example:

```groovy
class AppWithMockSpec extends Specification {
def mockS3Client = Mock(S3Client)
def app = new App(mockS3Client)
def "returns a list of buckets"() {
given: "a bucket exists"
1 * mockS3Client.listBuckets() >> listWithBucket()
when: "a request is received"
def request = getRequestFromFile()
def responseEvent = app.handleRequest(request, null)
then: "a list of buckets is returned"
def responseBody = new JsonSlurper().parseText(responseEvent.getBody()) as List
responseBody.size() >= 1
and: "the first item is the example bucket"
responseBody.first() == TEST_BUCKET_NAME
}
}
```

[[top]](#api-gateway-to-lambda-to-list-s3-buckets)

15 changes: 15 additions & 0 deletions java-test-samples/apigw-lambda-list-s3-buckets/metadata.json
Original file line number Diff line number Diff line change
@@ -21,6 +21,14 @@
{
"title": "Integration Test",
"filepath": "/src/test/java/com/example/AppTest.java"
},
{
"title": "Unit Tests - Spock",
"filepath": "/src/test/groovy/com/example/AppWithMockSpec.groovy"
},
{
"title": "Integration Test - Spock",
"filepath": "/src/test/groovy/com/example/AppSpecIT.groovy"
}
],
"authors": [
@@ -42,6 +50,13 @@
"image": "https://media.licdn.com/dms/image/C4E03AQEKB5q7oE1Vug/profile-displayphoto-shrink_400_400/0/1517729240352?e=1684368000&v=beta&t=gqSVXveSHrXQK9ZHNIKB1ihV8gjqjOZ2gMTeW3_86mQ",
"bio": "Senior Specialist Solutions Architect at AWS",
"linkedin": "https://www.linkedin.com/in/aneelmurari"
},
{
"name": "Matt Ridehalgh",
"image": "https://avatars.githubusercontent.com/u/9155962?v=4",
"bio": "Solutions Architect at AWS",
"linkedin": "https://www.linkedin.com/in/matthew-ridehalgh/",
"twitter": "https://twitter.com/mrideh"
}
]
}
30 changes: 24 additions & 6 deletions java-test-samples/apigw-lambda-list-s3-buckets/pom.xml
Original file line number Diff line number Diff line change
@@ -10,7 +10,8 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<junit.jupiter.version>5.9.0</junit.jupiter.version>
<junit.platform.version>1.9.0</junit.platform.version>
<junit.platform.version>1.9.2</junit.platform.version>
<spock.version>2.3-groovy-4.0</spock.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -34,12 +35,12 @@
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.1</version>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.11.0</version>
<version>3.11.1</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
@@ -132,25 +133,37 @@
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.307</version>
<version>1.12.470</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.1</version>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.1</version>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-tests</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<artifactId>spock-core</artifactId>
<groupId>org.spockframework</groupId>
<scope>test</scope>
<version>${spock.version}</version>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-json</artifactId>
<version>4.0.10</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
@@ -174,12 +187,17 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<includes>
<include>**/*Spec.groovy</include>
<include>**/*Test.java</include>
</includes>
<!-- These tests are excluded from sure fire plugin to avoid failures due to external dependencies.
You can run these tests individually if you have the relevant pre-requisites in place.
Please see README for more information on each test class file.
-->
<excludes>
<exclude>AppTest.java</exclude>
<exclude>AppSpecIT.groovy</exclude>
</excludes>
</configuration>
</plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example

import com.amazonaws.xray.AWSXRay
import groovy.json.JsonSlurper
import spock.lang.Specification

import static com.example.fixtures.ApiGwRequestFixtures.getRequestFromFile

class AppSpecIT extends Specification {

private static final String BAD_REQUEST_FILE = "events/apigw_req_s3_buckets_post.json"

def app = new App()

def "returns a list of buckets"() {
when: "a request is received"
def request = getRequestFromFile()
//This line manually adds the X-ray segment
AWSXRay.beginSegment("S3");
def responseEvent = app.handleRequest(request, null)

then: "a list of buckets is returned"
def responseBody = new JsonSlurper().parseText(responseEvent.getBody()) as List
responseBody.size() >= 1
}

def "method not supported response"() {
when: "a request is received"
def request = getRequestFromFile(BAD_REQUEST_FILE)

AWSXRay.beginSegment("S3")
def responseEvent = app.handleRequest(request, null)

then: "a method not supported response is returned"
responseEvent.getStatusCode() == 405
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example

import groovy.json.JsonSlurper
import software.amazon.awssdk.services.s3.S3Client
import spock.lang.Specification

import static com.example.fixtures.ApiGwRequestFixtures.getRequestFromFile
import static com.example.fixtures.BucketFixtures.TEST_BUCKET_NAME
import static com.example.fixtures.BucketFixtures.listWithBucket

class AppWithMockSpec extends Specification {


def mockS3Client = Mock(S3Client)
def app = new App(mockS3Client)

def "returns a list of buckets"() {
given: "a bucket exists"
1 * mockS3Client.listBuckets() >> listWithBucket()

when: "a request is received"
def request = getRequestFromFile()
def responseEvent = app.handleRequest(request, null)

then: "a list of buckets is returned"
def responseBody = new JsonSlurper().parseText(responseEvent.getBody()) as List
responseBody.size() >= 1

and: "the first item is the example bucket"
responseBody.first() == TEST_BUCKET_NAME
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.fixtures

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper

class ApiGwRequestFixtures {

private static String DEFAULT_REQUEST_PAYLOAD_PATH = "events/apigw_req_s3_buckets_get.json"
private static String TEST_RESOURCES_PATH = "src/test/resources/"

static def getRequestFromFile(String filePath = DEFAULT_REQUEST_PAYLOAD_PATH) {
def file = new File(TEST_RESOURCES_PATH + filePath)
return new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.readValue(file, APIGatewayProxyRequestEvent)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.fixtures

import software.amazon.awssdk.services.s3.model.Bucket
import software.amazon.awssdk.services.s3.model.ListBucketsResponse

class BucketFixtures {
public static final String TEST_BUCKET_NAME = "demo-bucket"

static listWithBucket(String bucketName = TEST_BUCKET_NAME) {
def bucket = makeBucket(TEST_BUCKET_NAME)

return ListBucketsResponse.builder()
.buckets(bucket)
.build()
}

static Bucket makeBucket(String bucketName) {
Bucket.builder()
.name(bucketName)
.build()
}
}

0 comments on commit 3c21de2

Please sign in to comment.