Refactor fail description.

This commit is contained in:
Filip Dolník 2016-07-07 18:48:49 +02:00
parent 97d56e0b6a
commit 39b233b766
11 changed files with 191 additions and 57 deletions

View File

@ -10,6 +10,7 @@
183D03FF1C4691C600EBAEF3 /* Cuckoo.h in Headers */ = {isa = PBXBuildFile; fileRef = 183D03FE1C4691C600EBAEF3 /* Cuckoo.h */; settings = {ATTRIBUTES = (Public, ); }; }; 183D03FF1C4691C600EBAEF3 /* Cuckoo.h in Headers */ = {isa = PBXBuildFile; fileRef = 183D03FE1C4691C600EBAEF3 /* Cuckoo.h */; settings = {ATTRIBUTES = (Public, ); }; };
183D04061C4691C600EBAEF3 /* Cuckoo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 183D03FB1C4691C600EBAEF3 /* Cuckoo.framework */; }; 183D04061C4691C600EBAEF3 /* Cuckoo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 183D03FB1C4691C600EBAEF3 /* Cuckoo.framework */; };
183D04161C46926A00EBAEF3 /* CuckooFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183D04151C46926A00EBAEF3 /* CuckooFunctions.swift */; }; 183D04161C46926A00EBAEF3 /* CuckooFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183D04151C46926A00EBAEF3 /* CuckooFunctions.swift */; };
DC1A82B51D2D6BD500A217F0 /* FailTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1A82B31D2D6A2100A217F0 /* FailTest.swift */; };
DC4094EE1D211563006FB137 /* StubFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4094EC1D211563006FB137 /* StubFunction.swift */; }; DC4094EE1D211563006FB137 /* StubFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4094EC1D211563006FB137 /* StubFunction.swift */; };
DC4094EF1D211563006FB137 /* StubThrowingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4094ED1D211563006FB137 /* StubThrowingFunction.swift */; }; DC4094EF1D211563006FB137 /* StubThrowingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4094ED1D211563006FB137 /* StubThrowingFunction.swift */; };
DC4094F31D211598006FB137 /* ToBeStubbedProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4094F11D211598006FB137 /* ToBeStubbedProperty.swift */; }; DC4094F31D211598006FB137 /* ToBeStubbedProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4094F11D211598006FB137 /* ToBeStubbedProperty.swift */; };
@ -82,6 +83,7 @@
183D04051C4691C600EBAEF3 /* CuckooTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CuckooTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 183D04051C4691C600EBAEF3 /* CuckooTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CuckooTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
183D040C1C4691C600EBAEF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 183D040C1C4691C600EBAEF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
183D04151C46926A00EBAEF3 /* CuckooFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CuckooFunctions.swift; sourceTree = "<group>"; }; 183D04151C46926A00EBAEF3 /* CuckooFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CuckooFunctions.swift; sourceTree = "<group>"; };
DC1A82B31D2D6A2100A217F0 /* FailTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FailTest.swift; path = ../../../Brightify/Cuckoo/Tests/FailTest.swift; sourceTree = "<group>"; };
DC4094EC1D211563006FB137 /* StubFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StubFunction.swift; path = Stubbing/StubFunction/StubFunction.swift; sourceTree = "<group>"; }; DC4094EC1D211563006FB137 /* StubFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StubFunction.swift; path = Stubbing/StubFunction/StubFunction.swift; sourceTree = "<group>"; };
DC4094ED1D211563006FB137 /* StubThrowingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StubThrowingFunction.swift; path = Stubbing/StubFunction/StubThrowingFunction.swift; sourceTree = "<group>"; }; DC4094ED1D211563006FB137 /* StubThrowingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StubThrowingFunction.swift; path = Stubbing/StubFunction/StubThrowingFunction.swift; sourceTree = "<group>"; };
DC4094F11D211598006FB137 /* ToBeStubbedProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ToBeStubbedProperty.swift; path = Stubbing/ToBeStubbedProperty/ToBeStubbedProperty.swift; sourceTree = "<group>"; }; DC4094F11D211598006FB137 /* ToBeStubbedProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ToBeStubbedProperty.swift; path = Stubbing/ToBeStubbedProperty/ToBeStubbedProperty.swift; sourceTree = "<group>"; };
@ -198,6 +200,7 @@
183D040C1C4691C600EBAEF3 /* Info.plist */, 183D040C1C4691C600EBAEF3 /* Info.plist */,
DC70D0A91D2AB5F500014C5F /* ClassTest.swift */, DC70D0A91D2AB5F500014C5F /* ClassTest.swift */,
DC70D0A61D2AB5B300014C5F /* CuckooFunctionsTest.swift */, DC70D0A61D2AB5B300014C5F /* CuckooFunctionsTest.swift */,
DC1A82B31D2D6A2100A217F0 /* FailTest.swift */,
DC70D0AC1D2AB62100014C5F /* ProtocolTest.swift */, DC70D0AC1D2AB62100014C5F /* ProtocolTest.swift */,
DC70D0B71D2ABA3100014C5F /* TestUtils.swift */, DC70D0B71D2ABA3100014C5F /* TestUtils.swift */,
DC70D0A01D2AB53D00014C5F /* Describing */, DC70D0A01D2AB53D00014C5F /* Describing */,
@ -547,6 +550,7 @@
DC70D0B31D2AB6B900014C5F /* MatchableTest.swift in Sources */, DC70D0B31D2AB6B900014C5F /* MatchableTest.swift in Sources */,
DC70D0BE1D2AF40800014C5F /* TestedClass.swift in Sources */, DC70D0BE1D2AF40800014C5F /* TestedClass.swift in Sources */,
DC70D0D21D2B007300014C5F /* GeneratedMocks.swift in Sources */, DC70D0D21D2B007300014C5F /* GeneratedMocks.swift in Sources */,
DC1A82B51D2D6BD500A217F0 /* FailTest.swift in Sources */,
DC70D0D41D2B026200014C5F /* VerificationTest.swift in Sources */, DC70D0D41D2B026200014C5F /* VerificationTest.swift in Sources */,
DC70D0AD1D2AB62100014C5F /* ProtocolTest.swift in Sources */, DC70D0AD1D2AB62100014C5F /* ProtocolTest.swift in Sources */,
DC70D0C51D2AFA8E00014C5F /* StubFunctionTest.swift in Sources */, DC70D0C51D2AFA8E00014C5F /* StubFunctionTest.swift in Sources */,

View File

@ -19,8 +19,8 @@ public func when<F>(function: F) -> F {
/// Creates object used for verification of calls. /// Creates object used for verification of calls.
@warn_unused_result @warn_unused_result
public func verify<M: Mock>(mock: M, _ callMatcher: CallMatcher = times(1), sourceLocation: SourceLocation = (#file, #line)) -> M.Verification { public func verify<M: Mock>(mock: M, _ callMatcher: CallMatcher = times(1), file: StaticString = #file, line: UInt = #line) -> M.Verification {
return mock.getVerificationProxy(callMatcher, sourceLocation: sourceLocation) return mock.getVerificationProxy(callMatcher, sourceLocation: (file, line))
} }
/// Clears all invocations and stubs of mocks. /// Clears all invocations and stubs of mocks.
@ -45,8 +45,8 @@ public func clearInvocations<M: Mock>(mocks: M...) {
} }
/// Checks if there are no more uverified calls. /// Checks if there are no more uverified calls.
public func verifyNoMoreInteractions<M: Mock>(mocks: M..., sourceLocation: SourceLocation = (#file, #line)) { public func verifyNoMoreInteractions<M: Mock>(mocks: M..., file: StaticString = #file, line: UInt = #line) {
mocks.forEach { mock in mocks.forEach { mock in
mock.manager.verifyNoMoreInteractions(sourceLocation) mock.manager.verifyNoMoreInteractions((file, line))
} }
} }

View File

@ -8,34 +8,35 @@
/// CallMatcher is used in verification to assert how many times was the call made. It can also be used to do different asserts on stub calls matched with parameter matchers. /// CallMatcher is used in verification to assert how many times was the call made. It can also be used to do different asserts on stub calls matched with parameter matchers.
public struct CallMatcher { public struct CallMatcher {
private let matchesFunction: [StubCall] throws -> Bool public let name: String
public init(matchesFunction: [StubCall] -> Bool = { _ in true }) { private let matchesFunction: [StubCall] -> Bool
public init(name: String, matchesFunction: [StubCall] -> Bool) {
self.name = name
self.matchesFunction = matchesFunction self.matchesFunction = matchesFunction
} }
public init(numberOfExpectedCalls: Int, compareCallsFunction: (expected: Int, actual: Int) -> Bool) { public init(name: String, numberOfExpectedCalls: Int, compareCallsFunction: (expected: Int, actual: Int) -> Bool) {
self.matchesFunction = { self.init(name: name) {
return compareCallsFunction(expected: numberOfExpectedCalls, actual: $0.count) return compareCallsFunction(expected: numberOfExpectedCalls, actual: $0.count)
} }
} }
public func matches(calls: [StubCall]) -> Bool { public func matches(calls: [StubCall]) -> Bool {
do { return matchesFunction(calls)
return try matchesFunction(calls)
} catch {
return false
}
} }
public func or(otherMatcher: CallMatcher) -> CallMatcher { public func or(otherMatcher: CallMatcher) -> CallMatcher {
return CallMatcher { let name = "either \(self.name) or \(otherMatcher.name)"
return CallMatcher(name: name) {
return self.matches($0) || otherMatcher.matches($0) return self.matches($0) || otherMatcher.matches($0)
} }
} }
public func and(otherMatcher: CallMatcher) -> CallMatcher { public func and(otherMatcher: CallMatcher) -> CallMatcher {
return CallMatcher { let name = "both \(self.name) and \(otherMatcher.name)"
return CallMatcher(name: name) {
return self.matches($0) && otherMatcher.matches($0) return self.matches($0) && otherMatcher.matches($0)
} }
} }

View File

@ -9,7 +9,8 @@
/// Returns a matcher ensuring a call was made **`count`** times. /// Returns a matcher ensuring a call was made **`count`** times.
@warn_unused_result @warn_unused_result
public func times(count: Int) -> CallMatcher { public func times(count: Int) -> CallMatcher {
return CallMatcher(numberOfExpectedCalls: count, compareCallsFunction: ==) let name = count == 0 ? "never" : "\(count) times"
return CallMatcher(name: name, numberOfExpectedCalls: count, compareCallsFunction: ==)
} }
/// Returns a matcher ensuring no call was made. /// Returns a matcher ensuring no call was made.
@ -27,11 +28,11 @@ public func atLeastOnce() -> CallMatcher {
/// Returns a matcher ensuring call was made at least `count` times. /// Returns a matcher ensuring call was made at least `count` times.
@warn_unused_result @warn_unused_result
public func atLeast(count: Int) -> CallMatcher { public func atLeast(count: Int) -> CallMatcher {
return CallMatcher(numberOfExpectedCalls: count, compareCallsFunction: <=) return CallMatcher(name: "at least \(count) times", numberOfExpectedCalls: count, compareCallsFunction: <=)
} }
/// Returns a matcher ensuring call was made at most `count` times. /// Returns a matcher ensuring call was made at most `count` times.
@warn_unused_result @warn_unused_result
public func atMost(count: Int) -> CallMatcher { public func atMost(count: Int) -> CallMatcher {
return CallMatcher(numberOfExpectedCalls: count, compareCallsFunction: >=) return CallMatcher(name: "at most \(count) times",numberOfExpectedCalls: count, compareCallsFunction: >=)
} }

View File

@ -8,7 +8,7 @@
/// ParameterMatcher matches parameters of methods in stubbing and verification. /// ParameterMatcher matches parameters of methods in stubbing and verification.
public struct ParameterMatcher<T>: Matchable { public struct ParameterMatcher<T>: Matchable {
private let matchesFunction: T throws -> Bool private let matchesFunction: T -> Bool
public init(matchesFunction: T -> Bool = { _ in true }) { public init(matchesFunction: T -> Bool = { _ in true }) {
self.matchesFunction = matchesFunction self.matchesFunction = matchesFunction
@ -19,10 +19,6 @@ public struct ParameterMatcher<T>: Matchable {
} }
public func matches(input: T) -> Bool { public func matches(input: T) -> Bool {
do { return matchesFunction(input)
return try matchesFunction(input)
} catch {
return false
}
} }
} }

View File

@ -84,8 +84,8 @@ public class MockManager {
unverifiedStubCallsIndexes = unverifiedStubCallsIndexes.filter { !indexesToRemove.contains($0) } unverifiedStubCallsIndexes = unverifiedStubCallsIndexes.filter { !indexesToRemove.contains($0) }
if callMatcher.matches(calls) == false { if callMatcher.matches(calls) == false {
let description = Description() let message = "Wanted \(callMatcher.name) but \(calls.count == 0 ? "not invoked" : "invoked \(calls.count) times")."
MockManager.fail(message: description.description, sourceLocation: sourceLocation) MockManager.fail(message: message, sourceLocation: sourceLocation)
} }
return __DoNotUse() return __DoNotUse()
} }
@ -106,14 +106,26 @@ public class MockManager {
func verifyNoMoreInteractions(sourceLocation: SourceLocation) { func verifyNoMoreInteractions(sourceLocation: SourceLocation) {
if unverifiedStubCallsIndexes.isEmpty == false { if unverifiedStubCallsIndexes.isEmpty == false {
let unverifiedCalls = unverifiedStubCallsIndexes.map { stubCalls[$0] }.map { String($0) }.joinWithSeparator(", ") let unverifiedCalls = unverifiedStubCallsIndexes.map { stubCalls[$0] }.map { call in
MockManager.fail(message: "Found unverified call(s): " + unverifiedCalls, sourceLocation: sourceLocation) if let bracketIndex = call.method.rangeOfString("(")?.startIndex {
let name = call.method.substringToIndex(bracketIndex)
return name + call.parametersAsString
} else {
if call.method.hasSuffix("#set") {
return call.method + call.parametersAsString
} else {
return call.method
}
}
}.enumerate().map { "\($0 + 1). " + $1 }.joinWithSeparator("\n")
let message = "No more interactions wanted but some found:\n"
MockManager.fail(message: message + unverifiedCalls, sourceLocation: sourceLocation)
} }
} }
@noreturn @noreturn
private func failAndCrash(message: String, sourceLocation: SourceLocation = (#file, #line)) { private func failAndCrash(message: String, file: StaticString = #file, line: UInt = #line) {
MockManager.fail(message: message, sourceLocation: sourceLocation) MockManager.fail(message: message, sourceLocation: (file, line))
fatalError(message) fatalError(message)
} }
} }

View File

@ -8,11 +8,23 @@
public protocol StubCall { public protocol StubCall {
var method: String { get } var method: String { get }
var parametersAsString: String { get }
} }
public struct ConcreteStubCall<IN>: StubCall { public struct ConcreteStubCall<IN>: StubCall {
public let method: String public let method: String
let parameters: IN public let parameters: IN
public var parametersAsString: String {
let string = String(parameters)
if (string.rangeOfString(",") != nil && string.hasPrefix("(")) || string == "()" {
return string
} else {
// If only one parameter add brackets and quotes
let wrappedParameter = String((parameters, 0))
return wrappedParameter.substringToIndex(wrappedParameter.endIndex.advancedBy(-4)) + ")"
}
}
public init(method: String, parameters: IN) { public init(method: String, parameters: IN) {
self.method = method self.method = method

View File

@ -64,19 +64,4 @@ class CuckooFunctionsTest: XCTestCase {
verifyNoMoreInteractions(mock) verifyNoMoreInteractions(mock)
} }
func testVerifyNoMoreInteractionsFail() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}
mock.noReturn()
verifyNoMoreInteractions(mock)
}
XCTAssertNotNil(error)
}
} }

133
Tests/FailTest.swift Normal file
View File

@ -0,0 +1,133 @@
//
// FailTest.swift
// Cuckoo
//
// Created by Filip Dolnik on 06.07.16.
// Copyright © 2016 Brightify. All rights reserved.
//
import XCTest
import Cuckoo
class FailTest: XCTestCase {
func testMissingInvocation() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
verify(mock).noReturn()
}
XCTAssertEqual(error, "Wanted 1 times but not invoked.")
}
func testNoInvocation2Wanted() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}
mock.noReturn()
verify(mock, times(2)).noReturn()
}
XCTAssertEqual(error, "Wanted 2 times but invoked 1 times.")
}
func testInvocationNeverWanted() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}
mock.noReturn()
verify(mock, never()).noReturn()
}
XCTAssertEqual(error, "Wanted never but invoked 1 times.")
}
func testInvocationAtLeast2Wanted() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}
mock.noReturn()
verify(mock, atLeast(2)).noReturn()
}
XCTAssertEqual(error, "Wanted at least 2 times but invoked 1 times.")
}
func test2InvocationAtMost1Wanted() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}
mock.noReturn()
mock.noReturn()
verify(mock, atMost(1)).noReturn()
}
XCTAssertEqual(error, "Wanted at most 1 times but invoked 2 times.")
}
func testCallMatcherOr() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}
verify(mock, times(1).or(times(2))).noReturn()
}
XCTAssertEqual(error, "Wanted either 1 times or 2 times but not invoked.")
}
func testCallMatcherAnd() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}
verify(mock, atLeast(1).and(atMost(2))).noReturn()
}
XCTAssertEqual(error, "Wanted both at least 1 times and at most 2 times but not invoked.")
}
func testVerifyNoMoreInteractionsFail() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass(spyOn: TestedClass())
mock.withOptionalClosure("a", closure: nil)
mock.noReturn()
mock.countCharacters("b")
let _ = mock.readWriteProperty
mock.readWriteProperty = 1
mock.withOptionalClosure("c", closure: { _ in })
verifyNoMoreInteractions(mock)
}
XCTAssertEqual(error, "No more interactions wanted but some found:\n" +
"1. withOptionalClosure(\"a\", nil)\n" +
"2. noReturn()\n" +
"3. countCharacters(\"b\")\n" +
"4. readWriteProperty#get\n" +
"5. readWriteProperty#set(1)\n" +
"6. withOptionalClosure(\"c\", Optional((Function)))")
}
}

View File

@ -17,7 +17,7 @@ class CallMatcherTest: XCTestCase {
} }
func testMatches() { func testMatches() {
let matcher = CallMatcher { ($0.first?.method ?? "") == "A"} let matcher = CallMatcher(name: "") { ($0.first?.method ?? "") == "A"}
let nonMatchingCalls = [ConcreteStubCall(method: "B", parameters: Void()) as StubCall] let nonMatchingCalls = [ConcreteStubCall(method: "B", parameters: Void()) as StubCall]
XCTAssertTrue(matcher.matches(call)) XCTAssertTrue(matcher.matches(call))

View File

@ -47,14 +47,4 @@ class VerificationTest: XCTestCase {
verify(mock).noReturn() verify(mock).noReturn()
verify(mock).countCharacters(anyString()) verify(mock).countCharacters(anyString())
} }
func testVerifyFail() {
let error = TestUtils.catchCuckooFail {
let mock = MockTestedClass()
verify(mock).noReturn()
}
XCTAssertNotNil(error)
}
} }