-
Notifications
You must be signed in to change notification settings - Fork 113
add a testing harness to ease testing of lambdas #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftAWSLambdaRuntime open source project | ||
// | ||
// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
// @testable for access of internal functions - this would only work for testing by design | ||
@testable import AWSLambdaRuntime | ||
import Dispatch | ||
import Logging | ||
import NIO | ||
|
||
extension Lambda { | ||
public static func test(_ closure: @escaping StringLambdaClosure, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm very excited about this API design. 😍 |
||
with payload: String, | ||
_ body: @escaping (Result<String, Error>) -> Void) { | ||
Self.test(StringLambdaClosureWrapper(closure), with: payload, body) | ||
} | ||
|
||
public static func test(_ closure: @escaping StringVoidLambdaClosure, | ||
with payload: String, | ||
_ body: @escaping (Result<Void, Error>) -> Void) { | ||
Self.test(StringVoidLambdaClosureWrapper(closure), with: payload, body) | ||
} | ||
|
||
public static func test<In: Decodable, Out: Encodable>(_ closure: @escaping CodableLambdaClosure<In, Out>, | ||
with payload: In, | ||
_ body: @escaping (Result<Out, Error>) -> Void) { | ||
Self.test(CodableLambdaClosureWrapper(closure), with: payload, body) | ||
} | ||
|
||
public static func test<In: Decodable>(_ closure: @escaping CodableVoidLambdaClosure<In>, | ||
with payload: In, | ||
_ body: @escaping (Result<Void, Error>) -> Void) { | ||
Self.test(CodableVoidLambdaClosureWrapper(closure), with: payload, body) | ||
} | ||
|
||
public static func test<In, Out, Handler: EventLoopLambdaHandler>(_ handler: Handler, | ||
with payload: In, | ||
_ body: @escaping (Result<Out, Error>) -> Void) where Handler.In == In, Handler.Out == Out { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to work with completion blocks here? I would be in favor of a sync api, which returns an public static func test<In, Out, Handler: EventLoopLambdaHandler>(
_ handler: Handler,
with payload: In) throws
-> Out where Handler.In == In, Handler.Out == Out Going for such a sync approach would safe developers from using This change should trickle down to all other methods of course. |
||
let logger = Logger(label: "test") | ||
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Going for a sync approach, will allow us to |
||
let context = Context(requestId: "\(DispatchTime.now().uptimeNanoseconds)", | ||
traceId: "Root=\(DispatchTime.now().uptimeNanoseconds);Parent=\(DispatchTime.now().uptimeNanoseconds);Sampled=1", | ||
invokedFunctionArn: "arn:aws:lambda:us-west-1:\(DispatchTime.now().uptimeNanoseconds):function:custom-runtime", | ||
deadline: .now() + 5, | ||
logger: logger, | ||
eventLoop: eventLoopGroup.next()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we maybe want to create a struct TestConfig {
let requestId: String
let traceId: String
let invokedFunctionArn: String
let timeout: Double
} that can be optionally injected into the |
||
handler.handle(context: context, payload: payload).whenComplete { result in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we execute this on the eventLoop as it would happen in the real environment? |
||
body(result) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftAWSLambdaRuntime open source project | ||
// | ||
// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import AWSLambdaRuntime | ||
import AWSLambdaTesting | ||
import XCTest | ||
|
||
class LambdaTestingTests: XCTestCase { | ||
func testCodableClosure() { | ||
struct Request: Codable { | ||
let name: String | ||
} | ||
|
||
struct Response: Codable { | ||
let message: String | ||
} | ||
|
||
let myLambda = { (_: Lambda.Context, request: Request, callback: (Result<Response, Error>) -> Void) in | ||
callback(.success(Response(message: "echo" + request.name))) | ||
} | ||
|
||
let request = Request(name: UUID().uuidString) | ||
Lambda.test(myLambda, with: request) { result in | ||
switch result { | ||
case .failure(let error): | ||
XCTFail("expected to succeed but failed with \(error)") | ||
case .success(let response): | ||
XCTAssertEqual(response.message, "echo" + request.name) | ||
} | ||
} | ||
} | ||
|
||
func testCodableVoidClosure() { | ||
struct Request: Codable { | ||
let name: String | ||
} | ||
|
||
let myLambda = { (_: Lambda.Context, _: Request, callback: (Result<Void, Error>) -> Void) in | ||
callback(.success(())) | ||
} | ||
|
||
let request = Request(name: UUID().uuidString) | ||
Lambda.test(myLambda, with: request) { result in | ||
switch result { | ||
case .failure(let error): | ||
XCTFail("expected to succeed but failed with \(error)") | ||
case .success: | ||
break | ||
} | ||
} | ||
} | ||
|
||
func testLambdaHandler() { | ||
struct Request: Codable { | ||
let name: String | ||
} | ||
|
||
struct Response: Codable { | ||
let message: String | ||
} | ||
|
||
struct MyLambda: LambdaHandler { | ||
typealias In = Request | ||
typealias Out = Response | ||
|
||
func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) { | ||
callback(.success(Response(message: "echo" + payload.name))) | ||
} | ||
} | ||
|
||
let request = Request(name: UUID().uuidString) | ||
Lambda.test(MyLambda(), with: request) { result in | ||
switch result { | ||
case .failure(let error): | ||
XCTFail("expected to succeed but failed with \(error)") | ||
case .success(let response): | ||
XCTAssertEqual(response.message, "echo" + request.name) | ||
} | ||
} | ||
} | ||
|
||
func testFailure() { | ||
struct MyError: Error {} | ||
|
||
struct MyLambda: LambdaHandler { | ||
typealias In = String | ||
typealias Out = Void | ||
|
||
func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) { | ||
callback(.failure(MyError())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid all those test cases work only because the callback is immediately called. |
||
} | ||
} | ||
|
||
Lambda.test(MyLambda(), with: UUID().uuidString) { result in | ||
switch result { | ||
case .failure(let error): | ||
XCTAssert(error is MyError) | ||
case .success: | ||
XCTFail("expected to fail but succeeded") | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can see the dependency is
NIO
and notNIOHTTP1
.