From 681ff651cba1af78a5d6da187f83e004b4c8ba98 Mon Sep 17 00:00:00 2001 From: kmcbride Date: Thu, 26 Aug 2021 12:25:04 -0700 Subject: [PATCH] Add more specific effect matchers (#178) --- MobiusNimble/Source/NimbleFirstMatchers.swift | 51 +++++ MobiusNimble/Source/NimbleNextMatchers.swift | 51 +++++ .../Test/NimbleFirstMatchersTests.swift | 144 ++++++++++++++ .../Test/NimbleNextMatchersTests.swift | 178 +++++++++++++++++ MobiusTest/Source/FirstMatchers.swift | 46 ++++- MobiusTest/Source/NextMatchers.swift | 45 ++++- MobiusTest/Test/FirstMatchersTests.swift | 124 +++++++++++- MobiusTest/Test/NextMatchersTests.swift | 182 ++++++++++++++++++ 8 files changed, 812 insertions(+), 9 deletions(-) diff --git a/MobiusNimble/Source/NimbleFirstMatchers.swift b/MobiusNimble/Source/NimbleFirstMatchers.swift index 22382f1a..414bed7c 100644 --- a/MobiusNimble/Source/NimbleFirstMatchers.swift +++ b/MobiusNimble/Source/NimbleFirstMatchers.swift @@ -97,3 +97,54 @@ public func haveEffects(_ 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(_ effects: [Effect]) -> Nimble.Predicate> { + return Nimble.Predicate>.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 haveExactlyEffects(_ effects: [Effect]) -> Nimble.Predicate> { + return Nimble.Predicate>.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)>" + ) + ) + }) +} diff --git a/MobiusNimble/Source/NimbleNextMatchers.swift b/MobiusNimble/Source/NimbleNextMatchers.swift index 9124d0e6..5d0544a4 100644 --- a/MobiusNimble/Source/NimbleNextMatchers.swift +++ b/MobiusNimble/Source/NimbleNextMatchers.swift @@ -127,3 +127,54 @@ public func haveEffects(_ 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(_ expected: [Effect]) -> Nimble.Predicate> { + return Nimble.Predicate>.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 haveExactlyEffects(_ expected: [Effect]) -> Nimble.Predicate> { + return Nimble.Predicate>.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)>" + ) + ) + }) +} diff --git a/MobiusNimble/Test/NimbleFirstMatchersTests.swift b/MobiusNimble/Test/NimbleFirstMatchersTests.swift index 5a93dcf3..af52fd1e 100644 --- a/MobiusNimble/Test/NimbleFirstMatchersTests.swift +++ b/MobiusNimble/Test/NimbleFirstMatchersTests.swift @@ -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(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(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(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(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? = 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(model: 3, effects: expectedEffects) + expect(first).to(haveExactlyEffects(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(model: 3, effects: actualEffects) + expect(first).to(haveExactlyEffects(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(model: 3, effects: actualEffects) + expect(first).to(haveExactlyEffects(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(model: 3, effects: actualEffects) + expect(first).to(haveExactlyEffects(expectedEffects)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains("equal <\(expectedEffects)>, got <\(actualEffects)>") + } + } + + context("when matching nil") { + beforeEach { + let first: First? = nil + expect(first).to(haveExactlyEffects([1])) + } + + it("should not match") { + assertionHandler.assertExpectationFailed() + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains(nextBeingNilNotAllowed) + } + } + } } } } diff --git a/MobiusNimble/Test/NimbleNextMatchersTests.swift b/MobiusNimble/Test/NimbleNextMatchersTests.swift index 718b7a75..28b6089e 100644 --- a/MobiusNimble/Test/NimbleNextMatchersTests.swift +++ b/MobiusNimble/Test/NimbleNextMatchersTests.swift @@ -425,6 +425,184 @@ class NimbleNextMatchersTests: QuickSpec { } } } + + context("when creating a matcher verifying that a Next has only specific effects") { + let expected = [1, 2, 3] + + context("when the effects are the same") { + beforeEach { + let next = Next.dispatchEffects(expected) + expect(next).to(haveOnlyEffects(expected)) + } + + it("should match") { + assertionHandler.assertExpectationSucceeded() + } + } + + context("when the effects are the same in different order") { + let actual = [3, 2, 1] + + beforeEach { + let next = Next.dispatchEffects(actual) + expect(next).to(haveOnlyEffects(expected)) + } + + it("should match") { + assertionHandler.assertExpectationSucceeded() + } + } + + context("when the Next contains the expected effects and a few more") { + let actual = [1, 2, 3, 4, 5, 0] + + beforeEach { + let next = Next.dispatchEffects(actual) + expect(next).to(haveOnlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains("contain only <\(expected)>, got <\(actual)>") + } + } + + context("when the Next does not contain one or more of the expected effects") { + let actual = [1, 2] + + beforeEach { + let next = Next.dispatchEffects(actual) + expect(next).to(haveOnlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains("contain only <\(expected)>, got <\(actual)>") + } + } + + context("when there are no effects") { + context("when not expecting effects") { + beforeEach { + let next = Next.noChange + expect(next).to(haveOnlyEffects([])) + } + + it("should match") { + assertionHandler.assertExpectationSucceeded() + } + } + + context("when expecting effects") { + beforeEach { + let next = Next.noChange + expect(next).to(haveOnlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains("contain only <\(expected)>, got <[]>") + } + } + } + + context("when matching nil") { + beforeEach { + let next: Next? = nil + expect(next).to(haveOnlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains(haveNonNilNext) + } + } + } + + context("when creating a matcher verifying that a Next has exact effects") { + let expected = [1, 2, 3] + + context("when the effects are the same") { + beforeEach { + let next = Next.dispatchEffects(expected) + expect(next).to(haveExactlyEffects(expected)) + } + + it("should match") { + assertionHandler.assertExpectationSucceeded() + } + } + + context("when the effects are the same in different order") { + let actual = [3, 2, 1] + + beforeEach { + let next = Next.dispatchEffects(actual) + expect(next).to(haveExactlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains("equal <\(expected)>, got <\(actual)>") + } + } + + context("when the Next contains the expected effects and a few more") { + let actual = [1, 2, 3, 4, 5, 0] + + beforeEach { + let next = Next.dispatchEffects(actual) + expect(next).to(haveExactlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains("equal <\(expected)>, got <\(actual)>") + } + } + + context("when the Next does not contain one or more of the expected effects") { + let actual = [1, 2] + + beforeEach { + let next = Next.dispatchEffects(actual) + expect(next).to(haveExactlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains("equal <\(expected)>, got <\(actual)>") + } + } + + context("when there are no effects") { + context("when not expecting effects") { + beforeEach { + let next = Next.noChange + expect(next).to(haveExactlyEffects([])) + } + + it("should match") { + assertionHandler.assertExpectationSucceeded() + } + } + + context("when expecting effects") { + beforeEach { + let next = Next.noChange + expect(next).to(haveExactlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains("equal <\(expected)>, got <[]>") + } + } + } + + context("when matching nil") { + beforeEach { + let next: Next? = nil + expect(next).to(haveExactlyEffects(expected)) + } + + it("should produce an appropriate error message") { + assertionHandler.assertLastErrorMessageContains(haveNonNilNext) + } + } + } } } } diff --git a/MobiusTest/Source/FirstMatchers.swift b/MobiusTest/Source/FirstMatchers.swift index d641c57e..980eb33e 100644 --- a/MobiusTest/Source/FirstMatchers.swift +++ b/MobiusTest/Source/FirstMatchers.swift @@ -94,11 +94,47 @@ public func hasEffects( ) -> FirstPredicate { return { (first: First) in if !expected.allSatisfy(first.effects.contains) { - return .failure( - message: "Expected effects <\(first.effects)> to contain <\(expected)>", - file: file, - line: line - ) + return .failure(message: "Expected <\(first.effects)> to contain <\(expected)>", file: file, line: line) + } + return .success + } +} + +/// Constructs a matcher that matches if only the supplied effects are present in the supplied `First`, in any order. +/// +/// - Parameter expected: the effects to match (possibly empty) +/// - Returns: a `Predicate` that matches `First` instances that include all the supplied effects +public func hasOnlyEffects( + _ expected: [Effect], + file: StaticString = #file, + line: UInt = #line +) -> FirstPredicate { + return { (first: First) in + var unmatchedActual = first.effects + var unmatchedExpected = expected + zip(first.effects, expected).forEach { + _ = unmatchedActual.firstIndex(of: $1).map { unmatchedActual.remove(at: $0) } + _ = unmatchedExpected.firstIndex(of: $0).map { unmatchedExpected.remove(at: $0) } + } + if !unmatchedActual.isEmpty || !unmatchedExpected.isEmpty { + return .failure(message: "Expected <\(first.effects)> to contain only <\(expected)>", file: file, line: line) + } + return .success + } +} + +/// Constructs a matcher that matches if the supplied effects are equal to the supplied `First`. +/// +/// - Parameter expected: the effects to match (possibly empty) +/// - Returns: a `Predicate` that matches `First` instances that include all the supplied effects +public func hasExactlyEffects( + _ expected: [Effect], + file: StaticString = #file, + line: UInt = #line +) -> FirstPredicate { + return { (first: First) in + if first.effects != expected { + return .failure(message: "Expected <\(first.effects)> to equal <\(expected)>", file: file, line: line) } return .success } diff --git a/MobiusTest/Source/NextMatchers.swift b/MobiusTest/Source/NextMatchers.swift index 88c64c26..139a81d5 100644 --- a/MobiusTest/Source/NextMatchers.swift +++ b/MobiusTest/Source/NextMatchers.swift @@ -132,9 +132,48 @@ public func hasEffects( line: UInt = #line ) -> NextPredicate { return { (next: Next) in - let actual = next.effects - if !expected.allSatisfy(actual.contains) { - return .failure(message: "Expected <\(actual)> to contain <\(expected)>", file: file, line: line) + if !expected.allSatisfy(next.effects.contains) { + return .failure(message: "Expected <\(next.effects)> to contain <\(expected)>", file: file, line: line) + } + return .success + } +} + +/// 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 hasOnlyEffects( + _ expected: [Effect], + file: StaticString = #file, + line: UInt = #line +) -> NextPredicate { + return { (next: Next) in + 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) } + } + if !unmatchedActual.isEmpty || !unmatchedExpected.isEmpty { + return .failure(message: "Expected <\(next.effects)> to contain only <\(expected)>", file: file, line: line) + } + return .success + } +} + +/// 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 hasExactlyEffects( + _ expected: [Effect], + file: StaticString = #file, + line: UInt = #line +) -> NextPredicate { + return { (next: Next) in + if next.effects != expected { + return .failure(message: "Expected <\(next.effects)> to equal <\(expected)>", file: file, line: line) } return .success } diff --git a/MobiusTest/Test/FirstMatchersTests.swift b/MobiusTest/Test/FirstMatchersTests.swift index 1fb94956..825d2c8f 100644 --- a/MobiusTest/Test/FirstMatchersTests.swift +++ b/MobiusTest/Test/FirstMatchersTests.swift @@ -159,7 +159,129 @@ class FirstMatchersTests: QuickSpec { } it("should fail with an appropriate error message") { - expect(result?.failureMessage).to(equal("Expected effects <\(actualEffects)> to contain <\(expectedEffects)>")) + expect(result?.failureMessage).to(equal("Expected <\(actualEffects)> to contain <\(expectedEffects)>")) + } + } + } + + 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(model: 3, effects: expectedEffects) + let sut: FirstPredicate = hasOnlyEffects(expectedEffects) + result = sut(first) + } + + it("should match") { + expect(result?.wasSuccessful).to(beTrue()) + } + } + + context("when the First has those effects in different order") { + let expectedEffects = [4, 7, 0] + let actualEffects = [0, 7, 4] + + beforeEach { + let first = First(model: 3, effects: actualEffects) + let sut: FirstPredicate = hasOnlyEffects(expectedEffects) + result = sut(first) + } + + it("should match") { + expect(result?.wasSuccessful).to(beTrue()) + } + } + + 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(model: 3, effects: actualEffects) + let sut: FirstPredicate = hasOnlyEffects(expectedEffects) + result = sut(first) + } + + it("should fail with an appropriate error message") { + expect(result?.failureMessage).to(equal("Expected <\(actualEffects)> to contain only <\(expectedEffects)>")) + } + } + + context("when the First does not contain all the expected effects") { + let expectedEffects = [10] + let actualEffects = [4] + + beforeEach { + let first = First(model: 3, effects: actualEffects) + let sut: FirstPredicate = hasOnlyEffects(expectedEffects) + result = sut(first) + } + + it("should fail with an appropriate error message") { + expect(result?.failureMessage).to(equal("Expected <\(actualEffects)> to contain only <\(expectedEffects)>")) + } + } + } + + 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(model: 3, effects: expectedEffects) + let sut: FirstPredicate = hasExactlyEffects(expectedEffects) + result = sut(first) + } + + it("should match") { + expect(result?.wasSuccessful).to(beTrue()) + } + } + + context("when the First has those effects in different order") { + let expectedEffects = [4, 7, 0] + let actualEffects = [0, 7, 4] + + beforeEach { + let first = First(model: 3, effects: actualEffects) + let sut: FirstPredicate = hasExactlyEffects(expectedEffects) + result = sut(first) + } + + it("should fail with an appropriate error message") { + expect(result?.failureMessage).to(equal("Expected <\(actualEffects)> to equal <\(expectedEffects)>")) + } + } + + 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(model: 3, effects: actualEffects) + let sut: FirstPredicate = hasExactlyEffects(expectedEffects) + result = sut(first) + } + + it("should fail with an appropriate error message") { + expect(result?.failureMessage).to(equal("Expected <\(actualEffects)> to equal <\(expectedEffects)>")) + } + } + + context("when the First does not contain all the expected effects") { + let expectedEffects = [10] + let actualEffects = [4] + + beforeEach { + let first = First(model: 3, effects: actualEffects) + let sut: FirstPredicate = hasExactlyEffects(expectedEffects) + result = sut(first) + } + + it("should fail with an appropriate error message") { + expect(result?.failureMessage).to(equal("Expected <\(actualEffects)> to equal <\(expectedEffects)>")) } } } diff --git a/MobiusTest/Test/NextMatchersTests.swift b/MobiusTest/Test/NextMatchersTests.swift index ef1bb906..eae3b657 100644 --- a/MobiusTest/Test/NextMatchersTests.swift +++ b/MobiusTest/Test/NextMatchersTests.swift @@ -348,6 +348,188 @@ class XCTestNextMatchersTests: QuickSpec { } } } + + context("when creating a matcher verifying that a Next has only specific effects") { + let expected = [1, 2, 3, 4] + var sut: NextPredicate! + + beforeEach { + sut = hasOnlyEffects(expected) + } + + context("when the effects are the same") { + context("when the effects are in order") { + beforeEach { + let next = Next.dispatchEffects(expected) + result = sut(next) + } + + it("should match") { + expect(result.wasSuccessful).to(beTrue()) + } + } + + context("when the effects are out of order") { + beforeEach { + var actual = expected + actual.append(actual.removeFirst()) + let next = Next.dispatchEffects(actual) + result = sut(next) + } + + it("should match") { + expect(result.wasSuccessful).to(beTrue()) + } + } + } + + context("when the Next contains the expected effects and a few more") { + let actual = [1, 2, 3, 4, 5, 0] + + beforeEach { + let next = Next.dispatchEffects(actual) + result = sut(next) + } + + it("should fail with an appropriate error message") { + expect(result.failureMessage).to(equal("Expected <\(actual)> to contain only <\(expected)>")) + } + } + + context("when the Next does not contain one or more of the expected effects") { + let actual = [1] + let expected = [3] + + beforeEach { + let next = Next.dispatchEffects(actual) + sut = hasOnlyEffects(expected) + result = sut(next) + } + + it("should fail with an appropriate error message") { + expect(result.failureMessage).to(equal("Expected <\(actual)> to contain only <\(expected)>")) + } + } + + context("when there are no effects") { + context("when not expecting effects") { + beforeEach { + let next = Next.noChange + let sut: NextPredicate = hasOnlyEffects([]) + result = sut(next) + } + + it("should match") { + expect(result.wasSuccessful).to(beTrue()) + } + } + + context("when expecting effects") { + let expected = [88] + + beforeEach { + let next = Next.noChange + sut = hasOnlyEffects(expected) + result = sut(next) + } + + it("should fail with an appropriate error message") { + expect(result.failureMessage).to(equal("Expected <[]> to contain only <\(expected)>")) + } + } + } + } + + context("when creating a matcher verifying that a Next has exact effects") { + let expected = [1, 2, 3, 4] + var sut: NextPredicate! + + beforeEach { + sut = hasExactlyEffects(expected) + } + + context("when the effects are the same") { + context("when the effects are in order") { + beforeEach { + let next = Next.dispatchEffects(expected) + result = sut(next) + } + + it("should match") { + expect(result.wasSuccessful).to(beTrue()) + } + } + + context("when the effects are out of order") { + let actual = [4, 3, 2, 1] + + beforeEach { + let next = Next.dispatchEffects(actual) + result = sut(next) + } + + it("should fail with an appropriate error message") { + expect(result.failureMessage).to(equal("Expected <\(actual)> to equal <\(expected)>")) + } + } + } + + context("when the Next contains the expected effects and a few more") { + let actual = [1, 2, 3, 4, 5, 0] + + beforeEach { + let next = Next.dispatchEffects(actual) + result = sut(next) + } + + it("should fail with an appropriate error message") { + expect(result.failureMessage).to(equal("Expected <\(actual)> to equal <\(expected)>")) + } + } + + context("when the Next does not contain one or more of the expected effects") { + let actual = [1] + let expected = [3] + + beforeEach { + let next = Next.dispatchEffects(actual) + sut = hasExactlyEffects(expected) + result = sut(next) + } + + it("should fail with an appropriate error message") { + expect(result.failureMessage).to(equal("Expected <\(actual)> to equal <\(expected)>")) + } + } + + context("when there are no effects") { + context("when not expecting effects") { + beforeEach { + let next = Next.noChange + let sut: NextPredicate = hasExactlyEffects([]) + result = sut(next) + } + + it("should match") { + expect(result.wasSuccessful).to(beTrue()) + } + } + + context("when expecting effects") { + let expected = [88] + + beforeEach { + let next = Next.noChange + sut = hasExactlyEffects(expected) + result = sut(next) + } + + it("should fail with an appropriate error message") { + expect(result.failureMessage).to(equal("Expected <[]> to equal <\(expected)>")) + } + } + } + } } } }