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 matchers for Result that match against submatchers, or for equatable values. #1134

Merged
merged 2 commits into from
Mar 30, 2024
Merged
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,14 @@ expect(aResult).to(beSuccess { value in
expect(value).to(equal("Hooray"))
})

// passes if the result value is .success and if the Success value matches
// the passed-in matcher (in this case, `equal`)
expect(aResult).to(beSuccess(equal("Hooray")))

// passes if the result value is .success and if the Success value equals
// the passed-in value (only available when the Success value is Equatable)
expect(aResult).to(beSuccess("Hooray"))


enum AnError: Error {
case somethingHappened
Expand All @@ -1529,6 +1537,10 @@ expect(otherResult).to(beFailure())
expect(otherResult).to(beFailure { error in
expect(error).to(matchError(AnError.somethingHappened))
})

// passes if the result value is .failure and if the Failure value matches
// the passed-in matcher (in this case, `matchError`)
expect(otherResult).to(beFailure(matchError(AnError.somethingHappened)))
```

> This matcher is only available in Swift.
Expand Down
87 changes: 87 additions & 0 deletions Sources/Nimble/Matchers/BeResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,60 @@ public func beSuccess<Success, Failure>(
}
}

/// A Nimble matcher for Result that succeeds when the actual value is success
/// and the value inside result is equal to the expected value
public func beSuccess<Success, Failure>(
_ value: Success
) -> Matcher<Result<Success, Failure>> where Success: Equatable {
return Matcher.define { expression in
let message = ExpectationMessage.expectedActualValueTo(
"be <success(\(Success.self))> that equals \(stringify(value))"
)

guard case let .success(resultValue)? = try expression.evaluate() else {
return MatcherResult(status: .doesNotMatch, message: message)
}

return MatcherResult(
bool: resultValue == value,
message: message
)
}
}

/// A Nimble matcher for Result that succeeds when the actual value is success
/// and the provided matcher matches.
public func beSuccess<Success, Failure>(
_ matcher: Matcher<Success>
) -> Matcher<Result<Success, Failure>> {
return Matcher.define { expression in
let message = ExpectationMessage.expectedActualValueTo(
"be <success(\(Success.self))> that satisfies matcher"
)

guard case let .success(value)? = try expression.evaluate() else {
return MatcherResult(status: .doesNotMatch, message: message)
}

let subExpression = Expression(
expression: { value },
location: expression.location
)
let subResult = try matcher.satisfies(subExpression)

let matches = subResult.toBoolean(expectation: .toMatch)

return MatcherResult(
bool: matches,
message: message.appended(
details: subResult.message.toString(
actual: stringify(value)
)
)
)
}
}

/// A Nimble matcher for Result that succeeds when the actual value is failure.
///
/// You can pass a closure to do any arbitrary custom matching to the error inside result.
Expand Down Expand Up @@ -65,3 +119,36 @@ public func beFailure<Success, Failure>(
return MatcherResult(bool: matches, message: message)
}
}

/// A Nimble matcher for Result that succeeds when the actual value is failure
/// and the provided matcher matches.
public func beFailure<Success, Failure>(
_ matcher: Matcher<Failure>
) -> Matcher<Result<Success, Failure>> {
return Matcher.define { expression in
let message = ExpectationMessage.expectedActualValueTo(
"be <failure(\(Failure.self))> that satisfies matcher"
)

guard case let .failure(error)? = try expression.evaluate() else {
return MatcherResult(status: .doesNotMatch, message: message)
}

let subExpression = Expression(
expression: { error },
location: expression.location
)
let subResult = try matcher.satisfies(subExpression)

let matches = subResult.toBoolean(expectation: .toMatch)

return MatcherResult(
bool: matches,
message: message.appended(
details: subResult.message.toString(
actual: stringify(error)
)
)
)
}
}
81 changes: 81 additions & 0 deletions Tests/NimbleTests/Matchers/BeResultTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,56 @@ final class BeSuccessTest: XCTestCase {
}
}

final class BeSuccessWithMatcherTest: XCTestCase {
func testPositiveMatch() {
let result: Result<Int, Error> = .success(1)
expect(result).to(beSuccess(equal(1)))
}

func testPositiveNegatedMatch() {
let result: Result<Int, Error> = .failure(StubError())
expect(result).toNot(beSuccess(equal(1)))

expect(Result<Int, Error>.success(2)).toNot(beSuccess(equal(1)))
}

func testNegativeMatches() {
failsWithErrorMessage("expected to be <success(Int)> that satisfies matcher, got <failure(StubError)>") {
let result: Result<Int, Error> = .failure(StubError())
expect(result).to(beSuccess(equal(1)))
}
failsWithErrorMessage("expected to be <success(Int)> that satisfies matcher, got <success(1)>\nexpected to equal <2>, got 1") {
let result: Result<Int, Error> = .success(1)
expect(result).to(beSuccess(equal(2)))
}
}
}

final class BeSuccessWithEquatableTest: XCTestCase {
func testPositiveMatch() {
let result: Result<Int, Error> = .success(1)
expect(result).to(beSuccess(1))
}

func testPositiveNegatedMatch() {
let result: Result<Int, Error> = .failure(StubError())
expect(result).toNot(beSuccess(1))

expect(Result<Int, Error>.success(2)).toNot(beSuccess(1))
}

func testNegativeMatches() {
failsWithErrorMessage("expected to be <success(Int)> that equals 1, got <failure(StubError)>") {
let result: Result<Int, Error> = .failure(StubError())
expect(result).to(beSuccess(1))
}
failsWithErrorMessage("expected to be <success(Int)> that equals 2, got <success(1)>") {
let result: Result<Int, Error> = .success(1)
expect(result).to(beSuccess(2))
}
}
}

final class BeFailureTest: XCTestCase {
func testPositiveMatch() {
let result: Result<Int, Error> = .failure(StubError())
Expand Down Expand Up @@ -105,3 +155,34 @@ final class BeFailureTest: XCTestCase {
}
}
}

final class BeFailureWithMatcherTest: XCTestCase {
func testPositiveMatch() {
let result: Result<Int, Error> = .failure(StubError())
expect(result).to(beFailure(matchError(StubError())))
}

func testPositiveNegatedMatch() {
let result: Result<Int, Error> = .success(1)
expect(result).toNot(beFailure(matchError(StubError())))

expect(
Result<Int, Error>.failure(TestError.foo)
).toNot(beFailure(matchError(StubError())))
}

func testNegativeMatches() {
failsWithErrorMessage("expected to be <failure(Error)> that satisfies matcher, got <success(1)>") {
let result: Result<Int, Error> = .success(1)
expect(result).to(beFailure(matchError(StubError())))
}
failsWithErrorMessage("expected to be <failure(Error)> that satisfies matcher, got <failure(StubError)>\nexpected to match error <TestError.foo>, got <StubError>") {
let result: Result<Int, Error> = .failure(StubError())
expect(result).to(beFailure(matchError(TestError.foo)))
}
failsWithErrorMessage("expected to be <failure(TestError)> that satisfies matcher, got <failure(TestError.foo)>\nexpected to equal <TestError.bar>, got TestError.foo") {
let result: Result<Int, TestError> = .failure(.foo)
expect(result).to(beFailure(equal(TestError.bar)))
}
}
}
Loading