Skip to content

Commit

Permalink
Add matchers for Result that match against submatchers, or for equata…
Browse files Browse the repository at this point in the history
…ble values. (Quick#1134)

* Add matchers for Result that match against submatchers, or for equatable values.

* Add documentation for the new matchers for result
  • Loading branch information
younata authored and noamfreeman committed Oct 14, 2024
1 parent fb47a08 commit 6e1eb9d
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 0 deletions.
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)))
}
}
}

0 comments on commit 6e1eb9d

Please sign in to comment.