Skip to content

Commit c69d8cb

Browse files
authored
Merge pull request #643 from xzeror/result_matchers
Adds matchers for Swift 5 Result type
2 parents 39d4d6d + 277b564 commit c69d8cb

File tree

4 files changed

+219
-0
lines changed

4 files changed

+219
-0
lines changed

Nimble.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
0477153523B740AD00402D4E /* DispatchTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0477153423B740AD00402D4E /* DispatchTimeInterval.swift */; };
2525
0477153623B740B700402D4E /* DispatchTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0477153423B740AD00402D4E /* DispatchTimeInterval.swift */; };
2626
0477153723B740B800402D4E /* DispatchTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0477153423B740AD00402D4E /* DispatchTimeInterval.swift */; };
27+
106112BD2251DFE7000A5848 /* BeResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106112BC2251DFE7000A5848 /* BeResult.swift */; };
28+
106112C02251E0FA000A5848 /* BeResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106112BC2251DFE7000A5848 /* BeResult.swift */; };
29+
106112C22251E0FD000A5848 /* BeResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106112BC2251DFE7000A5848 /* BeResult.swift */; };
30+
106112C52251E13B000A5848 /* BeResultTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106112C42251E13B000A5848 /* BeResultTest.swift */; };
31+
106112C62251E13B000A5848 /* BeResultTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106112C42251E13B000A5848 /* BeResultTest.swift */; };
32+
106112C72251E13B000A5848 /* BeResultTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106112C42251E13B000A5848 /* BeResultTest.swift */; };
2733
1F0648CC19639F5A001F9C46 /* ObjectWithLazyProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0648CB19639F5A001F9C46 /* ObjectWithLazyProperty.swift */; };
2834
1F0648CD19639F5A001F9C46 /* ObjectWithLazyProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0648CB19639F5A001F9C46 /* ObjectWithLazyProperty.swift */; };
2935
1F0648D41963AAB2001F9C46 /* SynchronousTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0648D31963AAB2001F9C46 /* SynchronousTest.swift */; };
@@ -504,6 +510,8 @@
504510

505511
/* Begin PBXFileReference section */
506512
0477153423B740AD00402D4E /* DispatchTimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchTimeInterval.swift; sourceTree = "<group>"; };
513+
106112BC2251DFE7000A5848 /* BeResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeResult.swift; sourceTree = "<group>"; };
514+
106112C42251E13B000A5848 /* BeResultTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeResultTest.swift; sourceTree = "<group>"; };
507515
1F0648CB19639F5A001F9C46 /* ObjectWithLazyProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectWithLazyProperty.swift; sourceTree = "<group>"; };
508516
1F0648D31963AAB2001F9C46 /* SynchronousTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronousTest.swift; sourceTree = "<group>"; };
509517
1F14FB63194180C5009F2A08 /* utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = utils.swift; sourceTree = "<group>"; };
@@ -830,6 +838,7 @@
830838
1F925F0A195C18E100ED456B /* BeLessThanTest.swift */,
831839
1F925EEE195C136500ED456B /* BeLogicalTest.swift */,
832840
1F925EF8195C175000ED456B /* BeNilTest.swift */,
841+
106112C42251E13B000A5848 /* BeResultTest.swift */,
833842
1F91DD2C1C74BF36002C309F /* BeVoidTest.swift */,
834843
857D184D2536123F00D8693A /* BeWithinTest.swift */,
835844
7B13BA091DD360DE00C9098C /* ContainElementSatisfyingTest.swift */,
@@ -883,6 +892,7 @@
883892
1FD8CD161968AB07008ED995 /* BeLessThanOrEqual.swift */,
884893
1FD8CD171968AB07008ED995 /* BeLogical.swift */,
885894
1FD8CD181968AB07008ED995 /* BeNil.swift */,
895+
106112BC2251DFE7000A5848 /* BeResult.swift */,
886896
1F91DD301C74BF61002C309F /* BeVoid.swift */,
887897
857D1848253610A900D8693A /* BeWithin.swift */,
888898
1FD8CD1A1968AB07008ED995 /* Contain.swift */,
@@ -1341,6 +1351,7 @@
13411351
CDFB6A401F7E082500AD8CC7 /* CwlDarwinDefinitions.swift in Sources */,
13421352
1FD8CD401968AB07008ED995 /* BeCloseTo.swift in Sources */,
13431353
1F1871C81CA89EDB00A34BF2 /* NMBExceptionCapture.m in Sources */,
1354+
106112C02251E0FA000A5848 /* BeResult.swift in Sources */,
13441355
1FD8CD361968AB07008ED995 /* Expectation.swift in Sources */,
13451356
1FD8CD321968AB07008ED995 /* NimbleXCTestHandler.swift in Sources */,
13461357
1F43728F1A1B344000EB80F8 /* Stringers.swift in Sources */,
@@ -1453,6 +1464,7 @@
14531464
1F299EAB19627B2D002641AF /* BeEmptyTest.swift in Sources */,
14541465
7B13BA111DD361EB00C9098C /* ObjCContainElementSatisfyingTest.m in Sources */,
14551466
1F925EF6195C147800ED456B /* BeCloseToTest.swift in Sources */,
1467+
106112C62251E13B000A5848 /* BeResultTest.swift in Sources */,
14561468
1F4A56791A3B32E3009E1637 /* ObjCBeGreaterThanOrEqualToTest.m in Sources */,
14571469
A8A3B6F6207329DD00E25A08 /* SatisfyAllOfTest.swift in Sources */,
14581470
AE7ADE491C80C00D00B94CD3 /* MatchErrorTest.swift in Sources */,
@@ -1530,6 +1542,7 @@
15301542
1F5DF17A1BDCA0F500C3A531 /* BeEmpty.swift in Sources */,
15311543
1F5DF18C1BDCA0F500C3A531 /* Await.swift in Sources */,
15321544
1F1871D81CA89EEF00A34BF2 /* NMBStringify.m in Sources */,
1545+
106112C22251E0FD000A5848 /* BeResult.swift in Sources */,
15331546
1F5DF1821BDCA0F500C3A531 /* BeNil.swift in Sources */,
15341547
1F5DF16F1BDCA0F500C3A531 /* AssertionDispatcher.swift in Sources */,
15351548
964CFEFF1C4FF48900513336 /* ThrowAssertion.swift in Sources */,
@@ -1595,6 +1608,7 @@
15951608
1F5DF1A61BDCA10200C3A531 /* EndWithTest.swift in Sources */,
15961609
CD79C9A31D2CC841004B6F9A /* ObjCBeFalseTest.m in Sources */,
15971610
1F5DF1A71BDCA10200C3A531 /* EqualTest.swift in Sources */,
1611+
106112C72251E13B000A5848 /* BeResultTest.swift in Sources */,
15981612
CD79C9AA1D2CC848004B6F9A /* ObjCBeLessThanOrEqualToTest.m in Sources */,
15991613
1F5DF1931BDCA10200C3A531 /* SynchronousTest.swift in Sources */,
16001614
CD79C9A11D2CC83B004B6F9A /* ObjCBeCloseToTest.m in Sources */,
@@ -1630,6 +1644,7 @@
16301644
CDFB6A3F1F7E082500AD8CC7 /* CwlDarwinDefinitions.swift in Sources */,
16311645
1FD8CD411968AB07008ED995 /* BeCloseTo.swift in Sources */,
16321646
1F1871D31CA89EEE00A34BF2 /* NMBExceptionCapture.m in Sources */,
1647+
106112BD2251DFE7000A5848 /* BeResult.swift in Sources */,
16331648
1FD8CD371968AB07008ED995 /* Expectation.swift in Sources */,
16341649
1FD8CD331968AB07008ED995 /* NimbleXCTestHandler.swift in Sources */,
16351650
1F43728E1A1B343F00EB80F8 /* Stringers.swift in Sources */,
@@ -1751,6 +1766,7 @@
17511766
857D184F2536124400D8693A /* BeWithinTest.swift in Sources */,
17521767
1F925EE7195C121200ED456B /* AsynchronousTest.swift in Sources */,
17531768
1F0648CD19639F5A001F9C46 /* ObjectWithLazyProperty.swift in Sources */,
1769+
106112C52251E13B000A5848 /* BeResultTest.swift in Sources */,
17541770
1F4A56861A3B33A0009E1637 /* ObjCBeTruthyTest.m in Sources */,
17551771
DD9A9A9019CF43AD00706F49 /* BeIdenticalToObjectTest.swift in Sources */,
17561772
1F4BB8B61DACA0E30048464B /* ThrowAssertionTest.swift in Sources */,

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ expect(ocean.isClean).toEventually(beTruthy())
5151
- [Collection Elements](#collection-elements)
5252
- [Collection Count](#collection-count)
5353
- [Notifications](#notifications)
54+
- [Result](#result)
5455
- [Matching a value to any of a group of matchers](#matching-a-value-to-any-of-a-group-of-matchers)
5556
- [Custom Validation](#custom-validation)
5657
- [Writing Your Own Matchers](#writing-your-own-matchers)
@@ -1197,6 +1198,37 @@ expect {
11971198

11981199
> This matcher is only available in Swift.
11991200
1201+
## Result
1202+
1203+
```swift
1204+
// Swift
1205+
let aResult: Result<String, Error> = .success("Hooray")
1206+
1207+
// passes if result is .success
1208+
expect(aResult).to(beSuccess())
1209+
1210+
// passes if result value is .success and validates Success value
1211+
expect(aResult).to(beSuccess { value in
1212+
expect(value).to(equal("Hooray"))
1213+
})
1214+
1215+
1216+
enum AnError: Error {
1217+
case somethingHappened
1218+
}
1219+
let otherResult: Result<String, AnError> = .failure(.somethingHappened)
1220+
1221+
// passes if result is .failure
1222+
expect(otherResult).to(beFailure())
1223+
1224+
// passes if result value is .failure and validates error
1225+
expect(otherResult).to(beFailure { error in
1226+
expect(error).to(matchError(AnError.somethingHappened))
1227+
})
1228+
```
1229+
1230+
> This matcher is only available in Swift.
1231+
12001232
## Matching a value to any of a group of matchers
12011233

12021234
```swift
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import Foundation
2+
3+
/// A Nimble matcher for Result that succeeds when the actual value is success.
4+
///
5+
/// You can pass a closure to do any arbitrary custom matching to the value inside result.
6+
/// The closure only gets called when the result is success.
7+
public func beSuccess<Success, Failure>(
8+
test: ((Success) -> Void)? = nil
9+
) -> Predicate<Result<Success, Failure>> {
10+
return Predicate.define { expression in
11+
var rawMessage = "be <success(\(Success.self))>"
12+
if test != nil {
13+
rawMessage += " that satisfies block"
14+
}
15+
let message = ExpectationMessage.expectedActualValueTo(rawMessage)
16+
17+
guard case let .success(value)? = try expression.evaluate() else {
18+
return PredicateResult(status: .doesNotMatch, message: message)
19+
}
20+
21+
var matches = true
22+
if let test = test {
23+
let assertions = gatherFailingExpectations {
24+
test(value)
25+
}
26+
let messages = assertions.map { $0.message }
27+
if !messages.isEmpty {
28+
matches = false
29+
}
30+
}
31+
32+
return PredicateResult(bool: matches, message: message)
33+
}
34+
}
35+
36+
/// A Nimble matcher for Result that succeeds when the actual value is failure.
37+
///
38+
/// You can pass a closure to do any arbitrary custom matching to the error inside result.
39+
/// The closure only gets called when the result is failure.
40+
public func beFailure<Success, Failure>(
41+
test: ((Failure) -> Void)? = nil
42+
) -> Predicate<Result<Success, Failure>> {
43+
return Predicate.define { expression in
44+
var rawMessage = "be <failure(\(Failure.self))>"
45+
if test != nil {
46+
rawMessage += " that satisfies block"
47+
}
48+
let message = ExpectationMessage.expectedActualValueTo(rawMessage)
49+
50+
guard case let .failure(error)? = try expression.evaluate() else {
51+
return PredicateResult(status: .doesNotMatch, message: message)
52+
}
53+
54+
var matches = true
55+
if let test = test {
56+
let assertions = gatherFailingExpectations {
57+
test(error)
58+
}
59+
let messages = assertions.map { $0.message }
60+
if !messages.isEmpty {
61+
matches = false
62+
}
63+
}
64+
65+
return PredicateResult(bool: matches, message: message)
66+
}
67+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import XCTest
2+
import Nimble
3+
4+
private struct StubError: Error, CustomDebugStringConvertible {
5+
let debugDescription = "StubError"
6+
}
7+
8+
enum TestError: Error, Equatable, CustomDebugStringConvertible {
9+
case foo, bar
10+
11+
var debugDescription: String {
12+
switch self {
13+
case .foo:
14+
return "TestError.foo"
15+
case .bar:
16+
return "TestError.bar"
17+
}
18+
}
19+
}
20+
21+
final class BeSuccessTest: XCTestCase {
22+
func testPositiveMatch() {
23+
let result: Result<Int, Error> = .success(1)
24+
expect(result).to(beSuccess())
25+
}
26+
27+
func testPositiveMatchWithClosure() {
28+
let stubValue = 1
29+
let result: Result<Int, Error> = .success(stubValue)
30+
expect(result).to(beSuccess { value in
31+
expect(value).to(equal(stubValue))
32+
})
33+
}
34+
35+
func testPositiveNegatedMatch() {
36+
let result: Result<Int, Error> = .failure(StubError())
37+
expect(result).toNot(beSuccess())
38+
}
39+
40+
func testNegativeMatches() {
41+
failsWithErrorMessage("expected to be <success(Int)>, got <failure(StubError)>") {
42+
let result: Result<Int, Error> = .failure(StubError())
43+
expect(result).to(beSuccess())
44+
}
45+
failsWithErrorMessage("expected to not be <success(Int)>, got <success(1)>") {
46+
let result: Result<Int, Error> = .success(1)
47+
expect(result).toNot(beSuccess())
48+
}
49+
50+
failsWithErrorMessage("expected to be <success(Int)> that satisfies block, got <success(1)>") {
51+
let result: Result<Int, Error> = .success(1)
52+
expect(result).to(beSuccess { _ in
53+
fail()
54+
})
55+
}
56+
}
57+
}
58+
59+
final class BeFailureTest: XCTestCase {
60+
func testPositiveMatch() {
61+
let result: Result<Int, Error> = .failure(StubError())
62+
expect(result).to(beFailure())
63+
}
64+
65+
func testPositiveMatchWithClosure() {
66+
let result: Result<Int, Error> = .failure(StubError())
67+
expect(result).to(beFailure { error in
68+
expect(error).to(matchError(StubError.self))
69+
})
70+
71+
expect(Result<Int, TestError>.failure(.foo)).to(beFailure { error in
72+
expect(error).to(equal(.foo))
73+
})
74+
}
75+
76+
func testPositiveNegatedMatch() {
77+
let result: Result<Int, Error> = .success(1)
78+
expect(result).toNot(beFailure())
79+
}
80+
81+
func testNegativeMatches() {
82+
failsWithErrorMessage("expected to be <failure(Error)>, got <success(1)>") {
83+
let result: Result<Int, Error> = .success(1)
84+
expect(result).to(beFailure())
85+
}
86+
failsWithErrorMessage("expected to not be <failure(Error)>, got <failure(StubError)>") {
87+
let result: Result<Int, Error> = .failure(StubError())
88+
expect(result).toNot(beFailure())
89+
}
90+
91+
failsWithErrorMessage("expected to be <failure(Error)> that satisfies block, got <failure(StubError)>") {
92+
let result: Result<Int, Error> = .failure(StubError())
93+
expect(result).to(beFailure { _ in
94+
fail()
95+
})
96+
}
97+
failsWithErrorMessage("expected to be <failure(TestError)> that satisfies block, got <failure(TestError.foo)>") {
98+
let result: Result<Int, TestError> = .failure(.foo)
99+
expect(result).to(beFailure { error in
100+
expect(error).to(equal(.bar))
101+
})
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)