-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This example demonstrates how to use a protocol to mock the AWS SDK for Swift, since Swift lacks the readwrite reflection required for traditional mocking. Passes the test (which is also the exemplar). There is no build available on the web since this example is not one that corresponds to a particular function, but instead a technique for Swift developers using the SDK. Once this PR is submitted, I'll start writing the AWS SDK for Swift Developer Guide chapter that will embed these snippets as it explains the mocking technique used here.
- Loading branch information
1 parent
9b1f117
commit c5d5019
Showing
6 changed files
with
351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// swift-tools-version:5.5 | ||
// The swift-tools-version declares the minimum version of Swift required to | ||
// build this package. | ||
// | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "mocking", | ||
dependencies: [ | ||
// Dependencies declare other packages that this package depends on. | ||
.package( | ||
url: "https://github.com/awslabs/aws-sdk-swift", | ||
from: "0.20.0" | ||
) | ||
], | ||
// snippet-start:[mocking.swift.package.targets] | ||
targets: [ | ||
// A target defines a module or a test suite. A target can depend on | ||
// other targets in this package. They can also depend on products in | ||
// other packages that this package depends on. | ||
.executableTarget( | ||
name: "mocking", | ||
dependencies: [ | ||
.product(name: "AWSS3", package: "aws-sdk-swift"), | ||
], | ||
path: "./Sources" | ||
), | ||
// snippet-start:[mocking.swift.package.testTarget] | ||
.testTarget( | ||
name: "mocking-tests", | ||
dependencies: [ | ||
.product(name: "AWSS3", package: "aws-sdk-swift"), | ||
"mocking" | ||
], | ||
path: "./Tests" | ||
) | ||
// snippet-end:[mocking.swift.package.testTarget] | ||
] | ||
// snippet-end:[mocking.swift.package.targets] | ||
) |
49 changes: 49 additions & 0 deletions
49
swift/example_code/swift-sdk/mocking/Sources/BucketManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// A class to manage Amazon Simple Storage Service (Amazon S3) operations | ||
// using the ``S3Session`` class to access S3. | ||
// | ||
|
||
import Foundation | ||
import ClientRuntime | ||
import AWSS3 | ||
|
||
/// A class that uses an object that uses an object of type | ||
/// ``S3SessionProtocol`` to access Amazon S3. | ||
// snippet-start:[mocking.swift.using-session.class] | ||
public class BucketManager { | ||
/// The object based on the ``S3SessionProtocol`` protocol through which to | ||
/// call SDK for swift functions. This may be either ``S3Session`` or | ||
/// ``MockS3Session``. | ||
var session: S3SessionProtocol | ||
|
||
/// Initialize the ``S3Manager`` to call Amazon S3 functions using the | ||
/// specified object that implements ``S3SessionProtocol``. | ||
/// | ||
/// - Parameter session: The session object to use when calling Amazon S3. | ||
// snippet-start:[mocking.swift.using-session.init] | ||
init(session: S3SessionProtocol) { | ||
self.session = session | ||
} | ||
// snippet-end:[mocking.swift.using-session.init] | ||
|
||
/// Return an array listing all of the user's buckets by calling the | ||
/// ``S3SessionProtocol`` function `listBuckets()`. | ||
/// | ||
/// - Returns: An array of bucket name strings. | ||
/// | ||
// snippet-start:[mocking.swift.using-session.calling] | ||
public func getBucketNames() async throws -> [String] { | ||
let output = try await session.listBuckets(input: ListBucketsInput()) | ||
|
||
guard let buckets = output.buckets else { | ||
return [] | ||
} | ||
|
||
return buckets.map { $0.name ?? "<unknown>" } | ||
} | ||
// snippet-end:[mocking.swift.using-session.calling] | ||
} | ||
// snippet-end:[mocking.swift.using-session.class] |
56 changes: 56 additions & 0 deletions
56
swift/example_code/swift-sdk/mocking/Sources/S3Session.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// A protocol and implementation to allow calling and mocking of the AWS SDK | ||
// for Swift's `S3Client.listBuckets(input:)` function. | ||
// | ||
|
||
import Foundation | ||
import ClientRuntime | ||
import AWSS3 | ||
|
||
// snippet-start:[mocking.swift.protocol] | ||
/// The S3SessionProtocol protocol describes the Amazon S3 functions this | ||
/// program uses during an S3 session. It needs to be implemented once to call | ||
/// through to the corresponding SDK for Swift functions, and a second time to | ||
/// instead return mock results. | ||
public protocol S3SessionProtocol { | ||
func listBuckets(input: ListBucketsInput) async throws | ||
-> ListBucketsOutputResponse | ||
} | ||
// snippet-end:[mocking.swift.protocol] | ||
|
||
/// An implementation of ``S3SessionProtocol`` that calls the equivalent | ||
/// functions in the AWS SDK for Swift. This class is used by the main program | ||
/// instead of calling the SDK directly. | ||
// snippet-start:[mocking.swift.session] | ||
public class S3Session: S3SessionProtocol { | ||
let client: S3Client | ||
let region: String | ||
|
||
/// Initialize the session to use the specified AWS Region. | ||
/// | ||
/// - Parameter region: The AWS Region to use. Default is `us-east-1`. | ||
init(region: String = "us-east-1") throws { | ||
self.region = region | ||
|
||
// Create an ``S3Client`` to use for AWS SDK for Swift calls. | ||
self.client = try S3Client(region: self.region) | ||
} | ||
|
||
/// Call through to the ``S3Client`` function `listBuckets()`. | ||
/// | ||
/// - Parameter input: The input to pass through to the SDK function | ||
/// `listBuckets()`. | ||
/// | ||
/// - Returns: A ``ListBucketsOutputResponse`` with the returned data. | ||
/// | ||
// snippet-start:[mocking.swift.implement-real] | ||
public func listBuckets(input: ListBucketsInput) async throws | ||
-> ListBucketsOutputResponse { | ||
return try await self.client.listBuckets(input: input) | ||
} | ||
// snippet-end:[mocking.swift.implement-real] | ||
} | ||
// snippet-end:[mocking.swift.session] |
59 changes: 59 additions & 0 deletions
59
swift/example_code/swift-sdk/mocking/Sources/mocking.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// An example demonstrating how to mock AWS SDK for Swift functions using | ||
// protocols. | ||
// | ||
|
||
import Foundation | ||
import ClientRuntime | ||
import AWSS3 | ||
|
||
/// The main entry point for the example is an asynchronous main function. | ||
@main | ||
struct MockingDemo { | ||
/// The static, asynchronous entry point for the program. | ||
static func main() async { | ||
// snippet-start:[mocking.swift.main-setup] | ||
/// An ``S3Session`` object that passes calls through to the SDK for | ||
/// Swift. | ||
let session: S3Session | ||
/// A ``BucketManager`` object that will be initialized to call the | ||
/// SDK using the session. | ||
let bucketMgr: BucketManager | ||
|
||
// Create the ``S3Session`` and a ``BucketManager`` that calls the SDK | ||
// using it. | ||
do { | ||
session = try S3Session(region: "us-east-1") | ||
bucketMgr = BucketManager(session: session) | ||
} catch { | ||
print("Unable to initialize access to Amazon S3.") | ||
return | ||
} | ||
// snippet-end:[mocking.swift.main-setup] | ||
|
||
// snippet-start:[mocking.swift.main-call] | ||
let bucketList: [String] | ||
|
||
do { | ||
bucketList = try await bucketMgr.getBucketNames() | ||
} catch { | ||
print("Unable to get the bucket list.") | ||
return | ||
} | ||
// snippet-end:[mocking.swift.main-call] | ||
|
||
// Print out a list of the bucket names. | ||
|
||
if bucketList.count != 0 { | ||
print("Found \(bucketList.count) buckets:") | ||
for name in bucketList { | ||
print(" \(name)") | ||
} | ||
} else { | ||
print("No buckets found.") | ||
} | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
swift/example_code/swift-sdk/mocking/Tests/MockS3Session.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// An implementation of ``S3SessionProtocol`` that returns mock data instead | ||
// of calling through to Amazon Web Services (AWS). | ||
|
||
import Foundation | ||
import ClientRuntime | ||
import AWSS3 | ||
|
||
@testable import mocking | ||
|
||
/// A structure format used to provide the data from which mock Amazon S3 | ||
/// buckets are built. | ||
struct MockBucketInfo { | ||
/// The bucket's name. | ||
var name: String | ||
/// The bucket's creation timestamp. | ||
var created: Date | ||
} | ||
|
||
// snippet-start:[mocking.swift.mocksession] | ||
/// The ``MockS3Session`` type implements ``S3SessionProtocol`` but instead of | ||
/// calling through to the AWS SDK for Swift, its implementation of SDK | ||
/// functions return mocked results. | ||
public class MockS3Session: S3SessionProtocol { | ||
/// An array of data used to construct the mock bucket descriptions. | ||
private(set) var mockInfo: [MockBucketInfo] | ||
/// An array of mock bucket descriptions as SDK `Bucket` objects. | ||
var mockBuckets: [S3ClientTypes.Bucket] = [] | ||
/// A date formatter to convert ISO format date strings into timestamps. | ||
let isoDateFormatter = ISO8601DateFormatter() | ||
|
||
/// Initialize the mock session with some pretend buckets. | ||
init() { | ||
self.mockInfo = [ | ||
MockBucketInfo( | ||
name: "swift", | ||
created: isoDateFormatter.date(from: "2014-06-02T11:45:00-07:00")! | ||
), | ||
MockBucketInfo( | ||
name: "amazon", | ||
created: isoDateFormatter.date(from: "1995-07-16T08:00:00-07:00")! | ||
), | ||
MockBucketInfo( | ||
name: "moon", | ||
created: isoDateFormatter.date(from: "1969-07-20T13:17:39-07:00")! | ||
) | ||
] | ||
|
||
// Construct an array of `S3ClientTypes.Bucket` objects containing the | ||
// mock bucket data. The bucket objects only contain the minimum data | ||
// needed to test against. Update this if additional bucket | ||
// information is used by the main program. | ||
|
||
for item in self.mockInfo { | ||
let bucket = S3ClientTypes.Bucket( | ||
creationDate: item.created, | ||
name: item.name | ||
) | ||
self.mockBuckets.append(bucket) | ||
} | ||
} | ||
|
||
/// Compare the specified names to the mock data and see if the names match. | ||
/// | ||
/// - Parameter names: An array of bucket names to compare against the | ||
/// expected names. | ||
/// | ||
/// - Returns: `true` if the names match. `false` if they don't. | ||
func checkBucketNames(names: [String]) -> Bool { | ||
let sortedMockNames = (self.mockInfo.map { $0.name }).sorted() | ||
|
||
return sortedMockNames == names.sorted() | ||
} | ||
|
||
// snippet-start:[mocking.swift.implement-mock] | ||
/// An implementation of the Amazon S3 function `listBuckets()` that | ||
/// returns the mock data instead of accessing AWS. | ||
/// | ||
/// - Parameter input: The input to the `listBuckets()` function. | ||
/// | ||
/// - Returns: A `ListBucketsOutputResponse` object containing the list of | ||
/// buckets. | ||
public func listBuckets(input: ListBucketsInput) async throws | ||
-> ListBucketsOutputResponse { | ||
let response = ListBucketsOutputResponse( | ||
buckets: self.mockBuckets, | ||
owner: nil | ||
) | ||
return response | ||
} | ||
// snippet-end:[mocking.swift.implement-mock] | ||
} | ||
// snippet-end:[mocking.swift.mocksession] |
49 changes: 49 additions & 0 deletions
49
swift/example_code/swift-sdk/mocking/Tests/mocking-tests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
// Tests for the AWS SDK for Swift example. This demonstrates how to mock SDK | ||
// functions. | ||
|
||
import XCTest | ||
import Foundation | ||
import ClientRuntime | ||
import AWSS3 | ||
|
||
@testable import mocking | ||
|
||
// snippet-start:[mocking.swift.tests] | ||
final class MockingTests: XCTestCase { | ||
/// The session to use for Amazon S3 calls. In this case, it's a mock | ||
/// implementation. | ||
var session: MockS3Session? = nil | ||
/// The ``BucketManager`` that uses the session to perform Amazon S3 | ||
/// operations. | ||
var bucketMgr: BucketManager? = nil | ||
|
||
/// Perform one-time initialization before executing any tests. | ||
override class func setUp() { | ||
super.setUp() | ||
SDKLoggingSystem.initialize(logLevel: .error) | ||
} | ||
|
||
/// Set up things that need to be done just before each | ||
/// individual test function is called. | ||
override func setUp() { | ||
super.setUp() | ||
|
||
// snippet-start:[mocking.swift.tests-setup] | ||
self.session = MockS3Session() | ||
self.bucketMgr = BucketManager(session: self.session!) | ||
// snippet-end:[mocking.swift.tests-setup] | ||
} | ||
|
||
// snippet-start:[mocking.swift.tests-call] | ||
/// Test that `getBucketNames()` returns the expected results. | ||
func testGetBucketNames() async throws { | ||
let returnedNames = try await self.bucketMgr!.getBucketNames() | ||
XCTAssertTrue(self.session!.checkBucketNames(names: returnedNames), | ||
"Bucket names don't match") | ||
} | ||
// snippet-end:[mocking.swift.tests-call] | ||
} | ||
// snippet-end:[mocking.swift.tests] |