Skip to content

Commit 76815fb

Browse files
committed
add a testing harness to ease testing of lambdas
motivation: make testing lambda easy changes: * add a AWSLambdaTesting module * add helper methods for testing different types of Lambda handlers / closures
1 parent dede451 commit 76815fb

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

Package.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ let package = Package(
88
.macOS(.v10_13),
99
],
1010
products: [
11+
// core library
1112
.library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]),
13+
// common AWS events
1214
.library(name: "AWSLambdaEvents", targets: ["AWSLambdaEvents"]),
15+
// for testing only
16+
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
1317
],
1418
dependencies: [
1519
.package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"),
@@ -26,6 +30,12 @@ let package = Package(
2630
.testTarget(name: "AWSLambdaRuntimeTests", dependencies: ["AWSLambdaRuntime"]),
2731
.target(name: "AWSLambdaEvents", dependencies: []),
2832
.testTarget(name: "AWSLambdaEventsTests", dependencies: ["AWSLambdaEvents"]),
33+
// testing helper
34+
.target(name: "AWSLambdaTesting", dependencies: [
35+
"AWSLambdaRuntime",
36+
.product(name: "NIOHTTP1", package: "swift-nio"),
37+
]),
38+
.testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]),
2939
// samples
3040
.target(name: "StringSample", dependencies: ["AWSLambdaRuntime"]),
3141
.target(name: "CodableSample", dependencies: ["AWSLambdaRuntime"]),
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// @testable for access of internal functions - this would only work for testing by design
2+
@testable import AWSLambdaRuntime
3+
import Dispatch
4+
import Logging
5+
import NIO
6+
7+
extension Lambda {
8+
public static func test(_ closure: @escaping StringLambdaClosure,
9+
with payload: String,
10+
_ body: @escaping (Result<String, Error>) -> Void) {
11+
Self.test(StringLambdaClosureWrapper(closure), with: payload, body)
12+
}
13+
14+
public static func test(_ closure: @escaping StringVoidLambdaClosure,
15+
with payload: String,
16+
_ body: @escaping (Result<Void, Error>) -> Void) {
17+
Self.test(StringVoidLambdaClosureWrapper(closure), with: payload, body)
18+
}
19+
20+
public static func test<In: Decodable, Out: Encodable>(_ closure: @escaping CodableLambdaClosure<In, Out>,
21+
with payload: In,
22+
_ body: @escaping (Result<Out, Error>) -> Void) {
23+
Self.test(CodableLambdaClosureWrapper(closure), with: payload, body)
24+
}
25+
26+
public static func test<In: Decodable>(_ closure: @escaping CodableVoidLambdaClosure<In>,
27+
with payload: In,
28+
_ body: @escaping (Result<Void, Error>) -> Void) {
29+
Self.test(CodableVoidLambdaClosureWrapper(closure), with: payload, body)
30+
}
31+
32+
public static func test<In, Out, Handler: EventLoopLambdaHandler>(_ handler: Handler,
33+
with payload: In,
34+
_ body: @escaping (Result<Out, Error>) -> Void) where Handler.In == In, Handler.Out == Out {
35+
let logger = Logger(label: "test")
36+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
37+
let context = Context(requestId: "\(DispatchTime.now().uptimeNanoseconds)",
38+
traceId: "Root=\(DispatchTime.now().uptimeNanoseconds);Parent=\(DispatchTime.now().uptimeNanoseconds);Sampled=1",
39+
invokedFunctionArn: "arn:aws:lambda:us-west-1:\(DispatchTime.now().uptimeNanoseconds):function:custom-runtime",
40+
deadline: .now() + 5,
41+
logger: logger,
42+
eventLoop: eventLoopGroup.next())
43+
handler.handle(context: context, payload: payload).whenComplete { result in
44+
body(result)
45+
}
46+
}
47+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import AWSLambdaRuntime
2+
import AWSLambdaTesting
3+
import XCTest
4+
5+
class LambdaTestingTests: XCTestCase {
6+
func testCodableClosure() {
7+
struct Request: Codable {
8+
let name: String
9+
}
10+
11+
struct Response: Codable {
12+
let message: String
13+
}
14+
15+
let myLambda = { (_: Lambda.Context, request: Request, callback: (Result<Response, Error>) -> Void) in
16+
callback(.success(Response(message: "echo" + request.name)))
17+
}
18+
19+
let request = Request(name: UUID().uuidString)
20+
Lambda.test(myLambda, with: request) { result in
21+
switch result {
22+
case .failure(let error):
23+
XCTFail("expected to succeed but failed with \(error)")
24+
case .success(let response):
25+
XCTAssertEqual(response.message, "echo" + request.name)
26+
}
27+
}
28+
}
29+
30+
func testCodableVoidClosure() {
31+
struct Request: Codable {
32+
let name: String
33+
}
34+
35+
let myLambda = { (_: Lambda.Context, _: Request, callback: (Result<Void, Error>) -> Void) in
36+
callback(.success(()))
37+
}
38+
39+
let request = Request(name: UUID().uuidString)
40+
Lambda.test(myLambda, with: request) { result in
41+
switch result {
42+
case .failure(let error):
43+
XCTFail("expected to succeed but failed with \(error)")
44+
case .success:
45+
break
46+
}
47+
}
48+
}
49+
50+
func testLambdaHandler() {
51+
struct Request: Codable {
52+
let name: String
53+
}
54+
55+
struct Response: Codable {
56+
let message: String
57+
}
58+
59+
struct MyLambda: LambdaHandler {
60+
typealias In = Request
61+
typealias Out = Response
62+
63+
func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) {
64+
callback(.success(Response(message: "echo" + payload.name)))
65+
}
66+
}
67+
68+
let request = Request(name: UUID().uuidString)
69+
Lambda.test(MyLambda(), with: request) { result in
70+
switch result {
71+
case .failure(let error):
72+
XCTFail("expected to succeed but failed with \(error)")
73+
case .success(let response):
74+
XCTAssertEqual(response.message, "echo" + request.name)
75+
}
76+
}
77+
}
78+
79+
func testFailure() {
80+
struct MyError: Error {}
81+
82+
struct MyLambda: LambdaHandler {
83+
typealias In = String
84+
typealias Out = Void
85+
86+
func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) {
87+
callback(.failure(MyError()))
88+
}
89+
}
90+
91+
Lambda.test(MyLambda(), with: UUID().uuidString) { result in
92+
switch result {
93+
case .failure(let error):
94+
XCTAssert(error is MyError)
95+
case .success:
96+
XCTFail("expected to fail but succeeded")
97+
}
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)