From 6b8acb33dd22d7dd711350f721f406679ca958b9 Mon Sep 17 00:00:00 2001 From: tom doron Date: Tue, 21 Apr 2020 13:34:11 -0700 Subject: [PATCH] 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 --- Package.swift | 10 ++ Sources/AWSLambdaTesting/Lambda+Testing.swift | 61 ++++++++++ Tests/AWSLambdaTestingTests/Tests.swift | 114 ++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 Sources/AWSLambdaTesting/Lambda+Testing.swift create mode 100644 Tests/AWSLambdaTestingTests/Tests.swift diff --git a/Package.swift b/Package.swift index cdd1453a..fe41b346 100644 --- a/Package.swift +++ b/Package.swift @@ -8,8 +8,12 @@ let package = Package( .macOS(.v10_13), ], products: [ + // core library .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), + // common AWS events .library(name: "AWSLambdaEvents", targets: ["AWSLambdaEvents"]), + // for testing only + .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"), @@ -26,6 +30,12 @@ let package = Package( .testTarget(name: "AWSLambdaRuntimeTests", dependencies: ["AWSLambdaRuntime"]), .target(name: "AWSLambdaEvents", dependencies: []), .testTarget(name: "AWSLambdaEventsTests", dependencies: ["AWSLambdaEvents"]), + // testing helper + .target(name: "AWSLambdaTesting", dependencies: [ + "AWSLambdaRuntime", + .product(name: "NIOHTTP1", package: "swift-nio"), + ]), + .testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]), // samples .target(name: "StringSample", dependencies: ["AWSLambdaRuntime"]), .target(name: "CodableSample", dependencies: ["AWSLambdaRuntime"]), diff --git a/Sources/AWSLambdaTesting/Lambda+Testing.swift b/Sources/AWSLambdaTesting/Lambda+Testing.swift new file mode 100644 index 00000000..64fa23fd --- /dev/null +++ b/Sources/AWSLambdaTesting/Lambda+Testing.swift @@ -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, + with payload: String, + _ body: @escaping (Result) -> Void) { + Self.test(StringLambdaClosureWrapper(closure), with: payload, body) + } + + public static func test(_ closure: @escaping StringVoidLambdaClosure, + with payload: String, + _ body: @escaping (Result) -> Void) { + Self.test(StringVoidLambdaClosureWrapper(closure), with: payload, body) + } + + public static func test(_ closure: @escaping CodableLambdaClosure, + with payload: In, + _ body: @escaping (Result) -> Void) { + Self.test(CodableLambdaClosureWrapper(closure), with: payload, body) + } + + public static func test(_ closure: @escaping CodableVoidLambdaClosure, + with payload: In, + _ body: @escaping (Result) -> Void) { + Self.test(CodableVoidLambdaClosureWrapper(closure), with: payload, body) + } + + public static func test(_ handler: Handler, + with payload: In, + _ body: @escaping (Result) -> Void) where Handler.In == In, Handler.Out == Out { + let logger = Logger(label: "test") + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + 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()) + handler.handle(context: context, payload: payload).whenComplete { result in + body(result) + } + } +} diff --git a/Tests/AWSLambdaTestingTests/Tests.swift b/Tests/AWSLambdaTestingTests/Tests.swift new file mode 100644 index 00000000..2e32d730 --- /dev/null +++ b/Tests/AWSLambdaTestingTests/Tests.swift @@ -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) -> 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) 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) -> 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) -> Void) { + callback(.failure(MyError())) + } + } + + 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") + } + } + } +}