Skip to content

Commit

Permalink
Add more specific effect matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
kmcbride committed Aug 22, 2021
1 parent 9919fa8 commit 8c486ae
Show file tree
Hide file tree
Showing 8 changed files with 812 additions and 9 deletions.
51 changes: 51 additions & 0 deletions MobiusNimble/Source/NimbleFirstMatchers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,54 @@ public func haveEffects<Model, Effect: Equatable>(_ effects: [Effect]) -> Nimble
)
})
}

/// Returns a `Predicate` that matches if only the supplied effects are present in the supplied `First`, in any order.
///
/// - Parameter effects: the effects to match (possibly empty)
/// - Returns: a `Predicate` that matches `First` instances that include all the supplied effects
public func haveOnlyEffects<Model, Effect: Equatable>(_ effects: [Effect]) -> Nimble.Predicate<First<Model, Effect>> {
return Nimble.Predicate<First<Model, Effect>>.define(matcher: { actualExpression -> Nimble.PredicateResult in
guard let first = try actualExpression.evaluate() else {
return unexpectedNilParameterPredicateResult
}

var unmatchedActual = first.effects
var unmatchedExpected = effects
zip(first.effects, effects).forEach {
_ = unmatchedActual.firstIndex(of: $1).map { unmatchedActual.remove(at: $0) }
_ = unmatchedExpected.firstIndex(of: $0).map { unmatchedExpected.remove(at: $0) }
}

let expectedDescription = String(describing: effects)
let actualDescription = String(describing: first.effects)
return PredicateResult(
bool: unmatchedActual.isEmpty && unmatchedExpected.isEmpty,
message: .expectedCustomValueTo(
"contain only <\(expectedDescription)>",
actual: "<\(actualDescription)> (order doesn't matter)"
)
)
})
}

/// Returns a `Predicate` that matches if the supplied effects are equal to the supplied `First`.
///
/// - Parameter effects: the effects to match (possibly empty)
/// - Returns: a `Predicate` that matches `First` instances that include all the supplied effects
public func haveExactEffects<Model, Effect: Equatable>(_ effects: [Effect]) -> Nimble.Predicate<First<Model, Effect>> {
return Nimble.Predicate<First<Model, Effect>>.define(matcher: { actualExpression -> Nimble.PredicateResult in
guard let first = try actualExpression.evaluate() else {
return unexpectedNilParameterPredicateResult
}

let expectedDescription = String(describing: effects)
let actualDescription = String(describing: first.effects)
return PredicateResult(
bool: effects == first.effects,
message: .expectedCustomValueTo(
"equal <\(expectedDescription)>",
actual: "<\(actualDescription)>"
)
)
})
}
51 changes: 51 additions & 0 deletions MobiusNimble/Source/NimbleNextMatchers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,54 @@ public func haveEffects<Model, Effect: Equatable>(_ expected: [Effect]) -> Nimbl
)
})
}

/// Constructs a matcher that matches if only the supplied effects are present in the supplied `Next`, in any order.
///
/// - Parameter expected: the effects to match (possibly empty)
/// - Returns: a `Predicate` that matches `Next` instances that include all the supplied effects
public func haveOnlyEffects<Model, Effect: Equatable>(_ expected: [Effect]) -> Nimble.Predicate<Next<Model, Effect>> {
return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in
guard let next = try actualExpression.evaluate() else {
return unexpectedNilParameterPredicate
}

var unmatchedActual = next.effects
var unmatchedExpected = expected
zip(next.effects, expected).forEach {
_ = unmatchedActual.firstIndex(of: $1).map { unmatchedActual.remove(at: $0) }
_ = unmatchedExpected.firstIndex(of: $0).map { unmatchedExpected.remove(at: $0) }
}

let expectedDescription = String(describing: expected)
let actualDescription = String(describing: next.effects)
return Nimble.PredicateResult(
bool: unmatchedActual.isEmpty && unmatchedExpected.isEmpty,
message: .expectedCustomValueTo(
"contain only <\(expectedDescription)>",
actual: "<\(actualDescription)> (order doesn't matter)"
)
)
})
}

/// Constructs a matcher that matches if the supplied effects are equal to the supplied `Next`.
///
/// - Parameter expected: the effects to match (possibly empty)
/// - Returns: a `Predicate` that matches `Next` instances that include all the supplied effects
public func haveExactEffects<Model, Effect: Equatable>(_ expected: [Effect]) -> Nimble.Predicate<Next<Model, Effect>> {
return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in
guard let next = try actualExpression.evaluate() else {
return unexpectedNilParameterPredicate
}

let expectedDescription = String(describing: expected)
let actualDescription = String(describing: next.effects)
return Nimble.PredicateResult(
bool: expected == next.effects,
message: .expectedCustomValueTo(
"equal <\(expectedDescription)>",
actual: "<\(actualDescription)>"
)
)
})
}
144 changes: 144 additions & 0 deletions MobiusNimble/Test/NimbleFirstMatchersTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,150 @@ class NimbleFirstMatchersTests: QuickSpec {
}
}
}

context("when creating a matcher to check that a First has only specific effects") {
context("when the First has those effects") {
let expectedEffects = [4, 7, 0]

beforeEach {
let first = First<Int, Int>(model: 3, effects: expectedEffects)
expect(first).to(haveOnlyEffects(expectedEffects))
}

it("should match") {
assertionHandler.assertExpectationSucceeded()
}
}

context("when the First has those effects out of order") {
let expectedEffects = [4, 7, 0]
let actualEffects = [0, 7, 4]

beforeEach {
let first = First<Int, Int>(model: 3, effects: actualEffects)
expect(first).to(haveOnlyEffects(expectedEffects))
}

it("should match") {
assertionHandler.assertExpectationSucceeded()
}
}

context("when the First contains the expected effects and a few more") {
let expectedEffects = [4, 7, 0]
let actualEffects = [1, 4, 7, 0]

beforeEach {
let first = First<Int, Int>(model: 3, effects: actualEffects)
expect(first).to(haveOnlyEffects(expectedEffects))
}

it("should produce an appropriate error message") {
assertionHandler.assertLastErrorMessageContains("contain only <\(expectedEffects)>, got <\(actualEffects)> (order doesn't matter)")
}
}

context("when the First does not contain all the expected effects") {
let expectedEffects = [4, 1]
let actualEffects = [1]

beforeEach {
let first = First<Int, Int>(model: 3, effects: actualEffects)
expect(first).to(haveOnlyEffects(expectedEffects))
}

it("should produce an appropriate error message") {
assertionHandler.assertLastErrorMessageContains("contain only <\(expectedEffects)>, got <\(actualEffects)> (order doesn't matter)")
}
}

context("when matching nil") {
beforeEach {
let first: First<Int, Int>? = nil
expect(first).to(haveOnlyEffects([1]))
}

it("should not match") {
assertionHandler.assertExpectationFailed()
}

it("should produce an appropriate error message") {
assertionHandler.assertLastErrorMessageContains(nextBeingNilNotAllowed)
}
}
}

context("when creating a matcher to check that a First has exact effects") {
context("when the First has those effects") {
let expectedEffects = [4, 7, 0]

beforeEach {
let first = First<Int, Int>(model: 3, effects: expectedEffects)
expect(first).to(haveExactEffects(expectedEffects))
}

it("should match") {
assertionHandler.assertExpectationSucceeded()
}
}

context("when the First has those effects out of order") {
let expectedEffects = [4, 7, 0]
let actualEffects = [0, 7, 4]

beforeEach {
let first = First<Int, Int>(model: 3, effects: actualEffects)
expect(first).to(haveExactEffects(expectedEffects))
}

it("should produce an appropriate error message") {
assertionHandler.assertLastErrorMessageContains("equal <\(expectedEffects)>, got <\(actualEffects)>")
}
}

context("when the First contains the expected effects and a few more") {
let expectedEffects = [4, 7, 0]
let actualEffects = [1, 4, 7, 0]

beforeEach {
let first = First<Int, Int>(model: 3, effects: actualEffects)
expect(first).to(haveExactEffects(expectedEffects))
}

it("should produce an appropriate error message") {
assertionHandler.assertLastErrorMessageContains("equal <\(expectedEffects)>, got <\(actualEffects)>")
}
}

context("when the First does not contain all the expected effects") {
let expectedEffects = [4, 1]
let actualEffects = [1]

beforeEach {
let first = First<Int, Int>(model: 3, effects: actualEffects)
expect(first).to(haveExactEffects(expectedEffects))
}

it("should produce an appropriate error message") {
assertionHandler.assertLastErrorMessageContains("equal <\(expectedEffects)>, got <\(actualEffects)>")
}
}

context("when matching nil") {
beforeEach {
let first: First<Int, Int>? = nil
expect(first).to(haveExactEffects([1]))
}

it("should not match") {
assertionHandler.assertExpectationFailed()
}

it("should produce an appropriate error message") {
assertionHandler.assertLastErrorMessageContains(nextBeingNilNotAllowed)
}
}
}
}
}
}
Loading

0 comments on commit 8c486ae

Please sign in to comment.