Skip to content

Commit 920302a

Browse files
authored
Improve testing utils (#60)
motivation: make testing lambda easy changes: * add a AWSLambdaTesting module * add helper methods for testing different types of Lambda handlers / closures
1 parent 378adf8 commit 920302a

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-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: "NIO", 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: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
// @testable for access of internal functions - this would only work for testing by design
16+
@testable import AWSLambdaRuntime
17+
import Dispatch
18+
import Logging
19+
import NIO
20+
21+
extension Lambda {
22+
public struct TestConfig {
23+
public var requestId: String
24+
public var traceId: String
25+
public var invokedFunctionArn: String
26+
public var timeout: DispatchTimeInterval
27+
28+
public init(requestId: String = "\(DispatchTime.now().uptimeNanoseconds)",
29+
traceId: String = "Root=\(DispatchTime.now().uptimeNanoseconds);Parent=\(DispatchTime.now().uptimeNanoseconds);Sampled=1",
30+
invokedFunctionArn: String = "arn:aws:lambda:us-west-1:\(DispatchTime.now().uptimeNanoseconds):function:custom-runtime",
31+
timeout: DispatchTimeInterval = .seconds(5)) {
32+
self.requestId = requestId
33+
self.traceId = traceId
34+
self.invokedFunctionArn = invokedFunctionArn
35+
self.timeout = timeout
36+
}
37+
}
38+
39+
public static func test(_ closure: @escaping StringLambdaClosure,
40+
with payload: String,
41+
using config: TestConfig = .init()) throws -> String {
42+
try Self.test(StringLambdaClosureWrapper(closure), with: payload, using: config)
43+
}
44+
45+
public static func test(_ closure: @escaping StringVoidLambdaClosure,
46+
with payload: String,
47+
using config: TestConfig = .init()) throws {
48+
_ = try Self.test(StringVoidLambdaClosureWrapper(closure), with: payload, using: config)
49+
}
50+
51+
public static func test<In: Decodable, Out: Encodable>(
52+
_ closure: @escaping CodableLambdaClosure<In, Out>,
53+
with payload: In,
54+
using config: TestConfig = .init()
55+
) throws -> Out {
56+
try Self.test(CodableLambdaClosureWrapper(closure), with: payload, using: config)
57+
}
58+
59+
public static func test<In: Decodable>(
60+
_ closure: @escaping CodableVoidLambdaClosure<In>,
61+
with payload: In,
62+
using config: TestConfig = .init()
63+
) throws {
64+
_ = try Self.test(CodableVoidLambdaClosureWrapper(closure), with: payload, using: config)
65+
}
66+
67+
public static func test<In, Out, Handler: EventLoopLambdaHandler>(
68+
_ handler: Handler,
69+
with payload: In,
70+
using config: TestConfig = .init()
71+
) throws -> Out where Handler.In == In, Handler.Out == Out {
72+
let logger = Logger(label: "test")
73+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
74+
defer {
75+
try! eventLoopGroup.syncShutdownGracefully()
76+
}
77+
let eventLoop = eventLoopGroup.next()
78+
let context = Context(requestId: config.requestId,
79+
traceId: config.traceId,
80+
invokedFunctionArn: config.invokedFunctionArn,
81+
deadline: .now() + config.timeout,
82+
logger: logger,
83+
eventLoop: eventLoop)
84+
85+
return try eventLoop.flatSubmit {
86+
handler.handle(context: context, payload: payload)
87+
}.wait()
88+
}
89+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AWSLambdaRuntime
16+
import AWSLambdaTesting
17+
import NIO
18+
import XCTest
19+
20+
class LambdaTestingTests: XCTestCase {
21+
func testCodableClosure() {
22+
struct Request: Codable {
23+
let name: String
24+
}
25+
26+
struct Response: Codable {
27+
let message: String
28+
}
29+
30+
let myLambda = { (_: Lambda.Context, request: Request, callback: (Result<Response, Error>) -> Void) in
31+
callback(.success(Response(message: "echo" + request.name)))
32+
}
33+
34+
let request = Request(name: UUID().uuidString)
35+
var response: Response?
36+
XCTAssertNoThrow(response = try Lambda.test(myLambda, with: request))
37+
XCTAssertEqual(response?.message, "echo" + request.name)
38+
}
39+
40+
func testCodableVoidClosure() {
41+
struct Request: Codable {
42+
let name: String
43+
}
44+
45+
let myLambda = { (_: Lambda.Context, _: Request, callback: (Result<Void, Error>) -> Void) in
46+
callback(.success(()))
47+
}
48+
49+
let request = Request(name: UUID().uuidString)
50+
XCTAssertNoThrow(try Lambda.test(myLambda, with: request))
51+
}
52+
53+
func testLambdaHandler() {
54+
struct Request: Codable {
55+
let name: String
56+
}
57+
58+
struct Response: Codable {
59+
let message: String
60+
}
61+
62+
struct MyLambda: LambdaHandler {
63+
typealias In = Request
64+
typealias Out = Response
65+
66+
func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) {
67+
XCTAssertFalse(context.eventLoop.inEventLoop)
68+
callback(.success(Response(message: "echo" + payload.name)))
69+
}
70+
}
71+
72+
let request = Request(name: UUID().uuidString)
73+
var response: Response?
74+
XCTAssertNoThrow(response = try Lambda.test(MyLambda(), with: request))
75+
XCTAssertEqual(response?.message, "echo" + request.name)
76+
}
77+
78+
func testEventLoopLambdaHandler() {
79+
struct MyLambda: EventLoopLambdaHandler {
80+
typealias In = String
81+
typealias Out = String
82+
83+
func handle(context: Lambda.Context, payload: String) -> EventLoopFuture<String> {
84+
XCTAssertTrue(context.eventLoop.inEventLoop)
85+
return context.eventLoop.makeSucceededFuture("echo" + payload)
86+
}
87+
}
88+
89+
let input = UUID().uuidString
90+
var result: String?
91+
XCTAssertNoThrow(result = try Lambda.test(MyLambda(), with: input))
92+
XCTAssertEqual(result, "echo" + input)
93+
}
94+
95+
func testFailure() {
96+
struct MyError: Error {}
97+
98+
struct MyLambda: LambdaHandler {
99+
typealias In = String
100+
typealias Out = Void
101+
102+
func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) {
103+
callback(.failure(MyError()))
104+
}
105+
}
106+
107+
XCTAssertThrowsError(try Lambda.test(MyLambda(), with: UUID().uuidString)) { error in
108+
XCTAssert(error is MyError)
109+
}
110+
}
111+
112+
func testAsyncLongRunning() {
113+
var executed: Bool = false
114+
let myLambda = { (_: Lambda.Context, _: String, callback: @escaping (Result<Void, Error>) -> Void) in
115+
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.5) {
116+
executed = true
117+
callback(.success(()))
118+
}
119+
}
120+
121+
XCTAssertNoThrow(try Lambda.test(myLambda, with: UUID().uuidString))
122+
XCTAssertTrue(executed)
123+
}
124+
125+
func testConfigValues() {
126+
let timeout: TimeInterval = 4
127+
let config = Lambda.TestConfig(
128+
requestId: UUID().uuidString,
129+
traceId: UUID().uuidString,
130+
invokedFunctionArn: "arn:\(UUID().uuidString)",
131+
timeout: .seconds(4)
132+
)
133+
134+
let myLambda = { (ctx: Lambda.Context, _: String, callback: @escaping (Result<Void, Error>) -> Void) in
135+
XCTAssertEqual(ctx.requestId, config.requestId)
136+
XCTAssertEqual(ctx.traceId, config.traceId)
137+
XCTAssertEqual(ctx.invokedFunctionArn, config.invokedFunctionArn)
138+
139+
let secondsSinceEpoch = Double(Int64(bitPattern: ctx.deadline.rawValue)) / -1_000_000_000
140+
XCTAssertEqual(Date(timeIntervalSince1970: secondsSinceEpoch).timeIntervalSinceNow, timeout, accuracy: 0.1)
141+
142+
callback(.success(()))
143+
}
144+
145+
XCTAssertNoThrow(try Lambda.test(myLambda, with: UUID().uuidString, using: config))
146+
}
147+
}

0 commit comments

Comments
 (0)