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

Add XCTAssertNoThrowAsync and XCTAssertThrowsErrorAsync #485

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 55 additions & 0 deletions Sources/XCTest/Public/XCTAssert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ private enum _XCTAssertion {
case `false`
case fail
case throwsError
case throwsErrorAsync
case noThrow
case noThrowAsync

var name: String? {
switch(self) {
Expand All @@ -48,7 +50,9 @@ private enum _XCTAssertion {
case .`true`: return "XCTAssertTrue"
case .`false`: return "XCTAssertFalse"
case .throwsError: return "XCTAssertThrowsError"
case .throwsErrorAsync: return "XCTAssertThrowsErrorAsync"
case .noThrow: return "XCTAssertNoThrow"
case .noThrowAsync: return "XCTAssertNoThrowAsync"
case .fail: return nil
}
}
Expand Down Expand Up @@ -105,6 +109,28 @@ private func _XCTEvaluateAssertion(_ assertion: _XCTAssertion, message: @autoclo
}
}

private func _XCTEvaluateAssertionAsync(_ assertion: _XCTAssertion, message: @autoclosure () async -> String, file: StaticString, line: UInt, expression: () async throws -> _XCTAssertionResult) async {
let result: _XCTAssertionResult
do {
result = try await expression()
} catch {
result = .unexpectedFailure(error)
}

switch result {
case .success:
return
default:
if let currentTestCase = XCTCurrentTestCase {
currentTestCase.recordFailure(
withDescription: "\(result.failureDescription(assertion)) - \(await message())",
inFile: String(describing: file),
atLine: Int(line),
expected: result.isExpected)
}
}
}

/// This function emits a test failure if the general `Boolean` expression passed
/// to it evaluates to `false`.
///
Expand Down Expand Up @@ -432,6 +458,24 @@ public func XCTAssertThrowsError<T>(_ expression: @autoclosure () throws -> T, _
}
}

public func XCTAssertThrowsErrorAsync<T>(_ expression: @autoclosure () async throws -> T, _ message: @autoclosure () async -> String = "", file: StaticString = #file, line: UInt = #line, _ errorHandler: (_ error: Swift.Error) async -> Void = { _ in }) async {
let rethrowsOverload: (() async throws -> T, () async -> String, StaticString, UInt, (Swift.Error) async throws -> Void) async throws -> Void = XCTAssertThrowsErrorAsync

try? await rethrowsOverload(expression, message, file, line, errorHandler)
}

public func XCTAssertThrowsErrorAsync<T>(_ expression: @autoclosure () async throws -> T, _ message: @autoclosure () async -> String = "", file: StaticString = #file, line: UInt = #line, _ errorHandler: (_ error: Swift.Error) async throws -> Void = { _ in }) async rethrows {
await _XCTEvaluateAssertionAsync(.throwsErrorAsync, message: await message(), file: file, line: line) {
do {
_ = try await expression()
return .expectedFailure("did not throw error")
} catch {
try await errorHandler(error)
return .success
}
}
}

public func XCTAssertNoThrow<T>(_ expression: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
_XCTEvaluateAssertion(.noThrow, message: message(), file: file, line: line) {
do {
Expand All @@ -442,3 +486,14 @@ public func XCTAssertNoThrow<T>(_ expression: @autoclosure () throws -> T, _ mes
}
}
}

public func XCTAssertNoThrowAsync<T>(_ expression: @autoclosure () async throws -> T, _ message: @autoclosure () async -> String = "", file: StaticString = #file, line: UInt = #line) async {
await _XCTEvaluateAssertionAsync(.noThrowAsync, message: await message(), file: file, line: line) {
do {
_ = try await expression()
return .success
} catch let error {
return .expectedFailure("threw error \"\(error)\"")
}
}
}
98 changes: 93 additions & 5 deletions Tests/Functional/ErrorHandling/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ class ErrorHandling: XCTestCase {
("test_shouldButDoesNotThrowErrorInAssertion", test_shouldButDoesNotThrowErrorInAssertion),
("test_shouldThrowErrorInAssertion", test_shouldThrowErrorInAssertion),
("test_throwsErrorInAssertionButFailsWhenCheckingError", test_throwsErrorInAssertionButFailsWhenCheckingError),


// Tests for XCTAssertThrowsErrorAsync
("test_shouldRethrowErrorFromHandlerAsync", asyncTest(test_shouldRethrowErrorFromHandlerAsync)),
("test_shouldNotRethrowWhenHandlerDoesNotThrowAsync", asyncTest(test_shouldNotRethrowWhenHandlerDoesNotThrowAsync)),
("test_shouldButDoesNotThrowErrorInAssertionAsync", asyncTest(test_shouldButDoesNotThrowErrorInAssertionAsync)),
("test_shouldThrowErrorInAssertionAsync", asyncTest(test_shouldThrowErrorInAssertionAsync)),
("test_throwsErrorInAssertionButFailsWhenCheckingErrorAsync", asyncTest(test_throwsErrorInAssertionButFailsWhenCheckingErrorAsync)),

// Tests for "testFoo() throws"
("test_canAndDoesThrowErrorFromTestMethod", test_canAndDoesThrowErrorFromTestMethod),
("test_canButDoesNotThrowErrorFromTestMethod", test_canButDoesNotThrowErrorFromTestMethod),
Expand All @@ -33,6 +40,10 @@ class ErrorHandling: XCTestCase {
("test_shouldNotThrowErrorDefiningSuccess", test_shouldNotThrowErrorDefiningSuccess),
("test_shouldThrowErrorDefiningFailure", test_shouldThrowErrorDefiningFailure),

// Tests for XCTAssertAsyncNoThrow
("test_shouldNotThrowErrorDefiningSuccessAsync", asyncTest(test_shouldNotThrowErrorDefiningSuccessAsync)),
("test_shouldThrowErrorDefiningFailureAsync", asyncTest(test_shouldThrowErrorDefiningFailureAsync)),

// Tests for XCTUnwrap
("test_shouldNotThrowErrorOnUnwrapSuccess", test_shouldNotThrowErrorOnUnwrapSuccess),
("test_shouldThrowErrorOnUnwrapFailure", test_shouldThrowErrorOnUnwrapFailure),
Expand All @@ -51,7 +62,10 @@ class ErrorHandling: XCTestCase {

func functionThatDoesNotThrowError() throws {
}


func functionThatDoesNotThrowErrorAsync() async throws {
}

enum SomeError: Swift.Error {
case anError(String)
case shouldNotBeReached
Expand All @@ -61,6 +75,10 @@ class ErrorHandling: XCTestCase {
throw SomeError.anError("an error message")
}

func functionThatDoesThrowErrorAsync() async throws {
throw SomeError.anError("an error message")
}

// CHECK: Test Case 'ErrorHandling.test_shouldRethrowErrorFromHandler' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+3]]: error: ErrorHandling.test_shouldRethrowErrorFromHandler : XCTAssertThrowsError threw error "anError\("an error message"\)" -
// CHECK: Test Case 'ErrorHandling.test_shouldRethrowErrorFromHandler' failed \(\d+\.\d+ seconds\)
Expand Down Expand Up @@ -118,6 +136,63 @@ class ErrorHandling: XCTestCase {
}
}

// CHECK: Test Case 'ErrorHandling.test_shouldRethrowErrorFromHandlerAsync' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+3]]: error: ErrorHandling.test_shouldRethrowErrorFromHandlerAsync : XCTAssertThrowsErrorAsync threw error "anError\("an error message"\)" -
// CHECK: Test Case 'ErrorHandling.test_shouldRethrowErrorFromHandlerAsync' failed \(\d+\.\d+ seconds\)
func test_shouldRethrowErrorFromHandlerAsync() async throws {
try await XCTAssertThrowsErrorAsync(await functionThatDoesThrowErrorAsync()) {_ in try await functionThatDoesThrowErrorAsync() }
}

// CHECK: Test Case 'ErrorHandling.test_shouldNotRethrowWhenHandlerDoesNotThrowAsync' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: Test Case 'ErrorHandling.test_shouldNotRethrowWhenHandlerDoesNotThrowAsync' passed \(\d+\.\d+ seconds\)
func test_shouldNotRethrowWhenHandlerDoesNotThrowAsync() async throws {
try await XCTAssertThrowsErrorAsync(await functionThatDoesThrowErrorAsync()) {_ in try await functionThatDoesNotThrowErrorAsync() }
}

// CHECK: Test Case 'ErrorHandling.test_shouldButDoesNotThrowErrorInAssertionAsync' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+3]]: error: ErrorHandling.test_shouldButDoesNotThrowErrorInAssertionAsync : XCTAssertThrowsErrorAsync failed: did not throw error -
// CHECK: Test Case 'ErrorHandling.test_shouldButDoesNotThrowErrorInAssertionAsync' failed \(\d+\.\d+ seconds\)
func test_shouldButDoesNotThrowErrorInAssertionAsync() async throws {
await XCTAssertThrowsErrorAsync(try await functionThatDoesNotThrowErrorAsync())
}

// CHECK: Test Case 'ErrorHandling.test_shouldThrowErrorInAssertionAsync' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: Test Case 'ErrorHandling.test_shouldThrowErrorInAssertionAsync' passed \(\d+\.\d+ seconds\)
func test_shouldThrowErrorInAssertionAsync() async throws {
await XCTAssertThrowsErrorAsync(try await functionThatDoesThrowErrorAsync()) { error in
guard let thrownError = error as? SomeError else {
XCTFail("Threw the wrong type of error")
return
}

switch thrownError {
case .anError(let message):
XCTAssertEqual(message, "an error message")
default:
XCTFail("Unexpected error: \(thrownError)")
}
}
}

// CHECK: Test Case 'ErrorHandling.test_throwsErrorInAssertionButFailsWhenCheckingErrorAsync' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+11]]: error: ErrorHandling.test_throwsErrorInAssertionButFailsWhenCheckingErrorAsync : XCTAssertEqual failed: \("an error message"\) is not equal to \(""\) -
// CHECK: Test Case 'ErrorHandling.test_throwsErrorInAssertionButFailsWhenCheckingErrorAsync' failed \(\d+\.\d+ seconds\)
func test_throwsErrorInAssertionButFailsWhenCheckingErrorAsync() async throws {
await XCTAssertThrowsErrorAsync(try await functionThatDoesThrowErrorAsync()) { error in
guard let thrownError = error as? SomeError else {
XCTFail("Threw the wrong type of error")
return
}

switch thrownError {
case .anError(let message):
XCTAssertEqual(message, "")
default:
XCTFail("Unexpected error: \(thrownError)")
}
}
}

// CHECK: Test Case 'ErrorHandling.test_canAndDoesThrowErrorFromTestMethod' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: \<EXPR\>:0: error: ErrorHandling.test_canAndDoesThrowErrorFromTestMethod : threw error "anError\("an error message"\)"
// CHECK: Test Case 'ErrorHandling.test_canAndDoesThrowErrorFromTestMethod' failed \(\d+\.\d+ seconds\)
Expand Down Expand Up @@ -156,6 +231,19 @@ class ErrorHandling: XCTestCase {
XCTAssertNoThrow(try functionThatDoesThrowError())
}

// CHECK: Test Case 'ErrorHandling.test_shouldNotThrowErrorDefiningSuccessAsync' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: Test Case 'ErrorHandling.test_shouldNotThrowErrorDefiningSuccessAsync' passed \(\d+\.\d+ seconds\)
func test_shouldNotThrowErrorDefiningSuccessAsync() async {
await XCTAssertNoThrowAsync(try await functionThatDoesNotThrowErrorAsync())
}

// CHECK: Test Case 'ErrorHandling.test_shouldThrowErrorDefiningFailureAsync' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+3]]: error: ErrorHandling.test_shouldThrowErrorDefiningFailureAsync : XCTAssertNoThrowAsync failed: threw error "anError\("an error message"\)" -
// CHECK: Test Case 'ErrorHandling.test_shouldThrowErrorDefiningFailureAsync' failed \(\d+\.\d+ seconds\)
func test_shouldThrowErrorDefiningFailureAsync() async {
await XCTAssertNoThrowAsync(try await functionThatDoesThrowErrorAsync())
}

func functionShouldReturnOptionalButThrows() throws -> String? {
throw SomeError.anError("an error message")
}
Expand Down Expand Up @@ -293,11 +381,11 @@ class ErrorHandling: XCTestCase {
}

// CHECK: Test Suite 'ErrorHandling' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: \t Executed \d+ tests, with \d+ failures \(6 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: \t Executed \d+ tests, with \d+ failures \(7 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds

XCTMain([testCase(ErrorHandling.allTests)])

// CHECK: Test Suite '.*\.xctest' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: \t Executed \d+ tests, with \d+ failures \(6 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: \t Executed \d+ tests, with \d+ failures \(7 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: Test Suite 'All tests' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: \t Executed \d+ tests, with \d+ failures \(6 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: \t Executed \d+ tests, with \d+ failures \(7 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds