Skip to content
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

Implement .willThrow() to mocked async methods #303

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Sources/MockingbirdFramework/Stubbing/Stubbing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,51 @@ extension StubbingManager where DeclarationType == ThrowingFunctionDeclaration {
}
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension StubbingManager where DeclarationType == ThrowingAsyncFunctionDeclaration {
/// Stub a mocked async method that throws with an error.
///
/// Stubbing allows you to define custom behavior for mocks to perform. Methods that throw or
/// rethrow errors can be stubbed with a throwable object.
///
/// ```swift
/// struct BirdError: Error {}
/// given(await bird.throwingMethod()).willThrow(BirdError())
/// ```
///
/// - Note: Methods overloaded by return type should chain `returning` with `willThrow` to
/// disambiguate the mocked declaration.
///
/// - Parameter error: A stubbed error object to throw.
/// - Returns: The current stubbing manager which can be used to chain additional stubs.
@discardableResult
public func willThrow(_ error: Error) -> Self {
return addImplementation({ () async throws -> ReturnType in throw error })
}

/// Disambiguate async throwing methods overloaded by return type.
///
/// Declarations for methods overloaded by return type and stubbed with `willThrow` cannot use
/// type inference and should be disambiguated.
///
/// ```swift
/// protocol Bird {
/// func fetchMessage<T>() async throws -> T // Overloaded generically
/// func fetchMessage() async throws -> String // Overloaded explicitly
/// func fetchMessage() async throws -> Data
/// }
///
/// given(await bird.fetchMessage())
/// .returning(String.self)
/// .willThrow(BirdError())
/// ```
///
/// - Parameter type: The return type of the declaration to stub.
public func returning(_ type: ReturnType.Type = ReturnType.self) -> Self {
return self
}
}

extension StubbingManager where ReturnType == Void {
/// Stub a mocked method or property that returns `Void`.
///
Expand Down
13 changes: 8 additions & 5 deletions Sources/MockingbirdTestsHost/AsyncMethods.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@ import Foundation
#if swift(>=5.5.2)
protocol AsyncProtocol {
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func asyncMethodVoid() async
func asyncMethod() async

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func asyncMethod() async -> Bool

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func asyncMethod(parameter: String) async -> Int


@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func asyncMethod(block: () async -> Bool) async

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func asyncThrowingMethod() async throws -> Int

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func asyncClosureMethod(block: () async -> Bool) async
func asyncThrowingMethod() async throws -> String

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func asyncClosureThrowingMethod(block: () async throws -> Bool) async throws -> Bool
func asyncThrowingMethod(block: () async throws -> Bool) async throws -> Bool
}
#endif
46 changes: 23 additions & 23 deletions Tests/MockingbirdTests/Framework/StubbingAsyncTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ class StubbingAsyncTests: BaseTestCase {
}

func testStubAsyncMethodVoid() async {
given(await asyncProtocol.asyncMethodVoid()).willReturn()
await asyncProtocolInstance.asyncMethodVoid()
verify(await asyncProtocol.asyncMethodVoid()).wasCalled()
given(await asyncProtocol.asyncMethod()).willReturn()
let _: Void = await asyncProtocolInstance.asyncMethod()
verify(await asyncProtocol.asyncMethod()).returning(Void.self).wasCalled()
}

func testStubAsyncMethod_returnsValue() async {
Expand All @@ -46,7 +46,7 @@ class StubbingAsyncTests: BaseTestCase {
let result: Bool = await asyncProtocolInstance.asyncMethod()

XCTAssertEqual(result, true)
verify(await asyncProtocol.asyncMethod()).wasCalled()
verify(await asyncProtocol.asyncMethod()).returning(Bool.self).wasCalled()
}

func testStubAsyncMethodWithParameter_returnsValue() async {
Expand All @@ -64,49 +64,49 @@ class StubbingAsyncTests: BaseTestCase {
let result: Int = try await asyncProtocolInstance.asyncThrowingMethod()

XCTAssertEqual(result, 1)
verify(await asyncProtocol.asyncThrowingMethod()).wasCalled()
verify(await asyncProtocol.asyncThrowingMethod()).returning(Int.self).wasCalled()
}

func testStubAsyncThrowingMethod_throwsError() async throws {
given(await asyncProtocol.asyncThrowingMethod()) ~> { () throws -> Int in throw FakeError() }
await XCTAssertThrowsAsyncError(try await asyncProtocolInstance.asyncThrowingMethod())
verify(await asyncProtocol.asyncThrowingMethod()).wasCalled()
given(await asyncProtocol.asyncThrowingMethod()).returning(Int.self).willThrow(FakeError())
await XCTAssertThrowsAsyncError(try await asyncProtocolInstance.asyncThrowingMethod() as Int)
verify(await asyncProtocol.asyncThrowingMethod()).returning(Int.self).wasCalled()
}

func testStubAsyncClosureMethod() async throws {
given(await asyncProtocol.asyncClosureMethod(block: any())).willReturn()
await asyncProtocolInstance.asyncClosureMethod(block: { true })
verify(await asyncProtocol.asyncClosureMethod(block: any())).wasCalled()
given(await asyncProtocol.asyncMethod(block: any())).willReturn()
await asyncProtocolInstance.asyncMethod(block: { true })
verify(await asyncProtocol.asyncMethod(block: any())).wasCalled()
}

func testStubAsyncClosureThrowingMethod_returnsValue() async throws {
given(await asyncProtocol.asyncClosureThrowingMethod(block: any())) ~> true
given(await asyncProtocol.asyncThrowingMethod(block: any())) ~> true

let result: Bool = try await asyncProtocolInstance.asyncClosureThrowingMethod(block: { false })
let result: Bool = try await asyncProtocolInstance.asyncThrowingMethod(block: { false })

XCTAssertTrue(result)
verify(await asyncProtocol.asyncClosureThrowingMethod(block: any())).wasCalled()
verify(await asyncProtocol.asyncThrowingMethod(block: any())).wasCalled()
}

func testStubAsyncClosureThrowingMethod_throwsError() async throws {
given(await asyncProtocol.asyncClosureThrowingMethod(block: any())) ~> { _ in throw FakeError() }
await XCTAssertThrowsAsyncError(try await asyncProtocolInstance.asyncClosureThrowingMethod(block: { true }))
verify(await asyncProtocol.asyncClosureThrowingMethod(block: any())).wasCalled()
given(await asyncProtocol.asyncThrowingMethod(block: any())) ~> { _ in throw FakeError() }
await XCTAssertThrowsAsyncError(try await asyncProtocolInstance.asyncThrowingMethod(block: { true }))
verify(await asyncProtocol.asyncThrowingMethod(block: any())).wasCalled()
}

func testStubAsyncClosureThrowingMethod_returnsValueFromBlock() async throws {
given(await asyncProtocol.asyncClosureThrowingMethod(block: any())) ~> { try await $0() }
given(await asyncProtocol.asyncThrowingMethod(block: any())) ~> { try await $0() }

let result: Bool = try await asyncProtocolInstance.asyncClosureThrowingMethod(block: { true })
let result: Bool = try await asyncProtocolInstance.asyncThrowingMethod(block: { true })

XCTAssertTrue(result)
verify(await asyncProtocol.asyncClosureThrowingMethod(block: any())).wasCalled()
verify(await asyncProtocol.asyncThrowingMethod(block: any())).wasCalled()
}

func testStubAsyncClosureThrowingMethod_throwsErrorFromBlock() async throws {
given(await asyncProtocol.asyncClosureThrowingMethod(block: any())) ~> { try await $0() }
await XCTAssertThrowsAsyncError(try await asyncProtocolInstance.asyncClosureThrowingMethod(block: { throw FakeError() }))
verify(await asyncProtocol.asyncClosureThrowingMethod(block: any())).wasCalled()
given(await asyncProtocol.asyncThrowingMethod(block: any())) ~> { try await $0() }
await XCTAssertThrowsAsyncError(try await asyncProtocolInstance.asyncThrowingMethod(block: { throw FakeError() }))
verify(await asyncProtocol.asyncThrowingMethod(block: any())).wasCalled()
}

}
Expand Down