Match specific errors in action with Result #3040
-
TL;DR: in a post- This is also similar to #2646, but a little more focused on equatable errors. I have an action that looks like this: case someOperationResult(Result<User, Error>) Since we don't have typed throws yet, it's not convenient for me to catch a specific thrown error, so I'd prefer not to constrain the failure parameter to something I'd like to write a test that checks that a particular error is received. (I'm prepared to entertain arguments why I shouldn't be doing this. Maybe that's the answer here?) In non-TCA projects, I've done this: extension XCTestCase {
func assertThrowsSpecificError<ReturnValue, ErrorType: Equatable>(_ expression: @autoclosure () throws -> ReturnValue, _ expectedError: ErrorType, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line, _ errorHandler: (Error) -> Void = { _ in }) {
do {
_ = try expression()
}
catch let error as ErrorType {
XCTAssertEqual(error, expectedError, file: file, line: line)
return
}
catch {
XCTFail("Expected error of type \(ErrorType.self), but expression threw \(error) of type \(type(of: error))")
return
}
XCTFail("Failed to throw error, but expected error of type \(ErrorType.self)", file: file, line: line)
}
} usage: assertThrowsSpecificError(
try someThrowingOperation(),
SomeSpecificError(with: "specific data")
) I'd like to do something similar in a TCA test. But currently I'm getting this: await store.receive(\.someOperationResult.failure, SomeSpecificError())
^Cannot convert value of type 'KeyPath<Case<MyFeature.Action>, Case<any Error>>' to expected argument type 'KeyPath<Case<MyFeature.Action>, Case<SomeSpecificError>>' Is there a built-in way to do this? If not, I'd like to propose writing one. I think I can write my own by wrapping the version of Or is the answer here that I shouldn't be testing errors that are being thrown by my dependencies, since I'm not actually testing the dependencies themselves in this test? (I'm modernizing some code that someone else wrote, so I'm not completely familiar with the ins and outs of what the code or tests do; I'm just trying to stay faithful to the original tests, which used |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Update: I wrote this and it seems to work. As @lukeredpath mentions in #2646, it would be nice to have it built into the library so it could give better diagnostics when the error doesn't match. Edit: now with code attached: import ComposableArchitecture
extension TestStore {
/// Allows you to assert that the test store received an action with a `Result` containing the specified `Error`. The `Error` type must be `Equatable`.
/// See: <https://github.com/pointfreeco/swift-composable-architecture/discussions/3040>
func receive<ExpectedError: Error & Equatable>(
_ errorCaseKeyPath: CaseKeyPath<Action, Error>,
_ expectedError: ExpectedError,
timeout duration: Duration = .zero,
assert updateStateToExpectedResult: ((_ state: inout State) throws -> Void)? = nil,
file: StaticString = #file,
line: UInt = #line
) async where State: Equatable, Action: CasePathable {
await receive(
{ action in
guard let error = action[case: errorCaseKeyPath] as? ExpectedError else {
return false
}
return error == expectedError
},
timeout: duration,
assert: updateStateToExpectedResult,
file: file,
line: line
)
}
} |
Beta Was this translation helpful? Give feedback.
TestStore.receive
is unfortunately already very overloaded and we're looking forward to 2.0 when we can delete some of the older versions, so I'd worry about adding another overload here.My hunch here, though, is that the action containing this enum should massage the failure into an equatable one if it's important for testability:
Or it might be appropriate to use the closure-based
receive
to test things dir…