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, ); }; };
183D04061C4691C600EBAEF3 /* Cuckoo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 183D03FB1C4691C600EBAEF3 /* Cuckoo.framework */; };
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 */; };
DC4094EF1D211563006FB137 /* StubThrowingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4094ED1D211563006FB137 /* StubThrowingFunction.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; };
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>"; };
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>"; };
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>"; };
@ -198,6 +200,7 @@
183D040C1C4691C600EBAEF3 /* Info.plist */,
DC70D0A91D2AB5F500014C5F /* ClassTest.swift */,
DC70D0A61D2AB5B300014C5F /* CuckooFunctionsTest.swift */,
DC1A82B31D2D6A2100A217F0 /* FailTest.swift */,
DC70D0AC1D2AB62100014C5F /* ProtocolTest.swift */,
DC70D0B71D2ABA3100014C5F /* TestUtils.swift */,
DC70D0A01D2AB53D00014C5F /* Describing */,
@ -547,6 +550,7 @@
DC70D0B31D2AB6B900014C5F /* MatchableTest.swift in Sources */,
DC70D0BE1D2AF40800014C5F /* TestedClass.swift in Sources */,
DC70D0D21D2B007300014C5F /* GeneratedMocks.swift in Sources */,
DC1A82B51D2D6BD500A217F0 /* FailTest.swift in Sources */,
DC70D0D41D2B026200014C5F /* VerificationTest.swift in Sources */,
DC70D0AD1D2AB62100014C5F /* ProtocolTest.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.
@warn_unused_result
public func verify<M: Mock>(mock: M, _ callMatcher: CallMatcher = times(1), sourceLocation: SourceLocation = (#file, #line)) -> M.Verification {
return mock.getVerificationProxy(callMatcher, sourceLocation: sourceLocation)
public func verify<M: Mock>(mock: M, _ callMatcher: CallMatcher = times(1), file: StaticString = #file, line: UInt = #line) -> M.Verification {
return mock.getVerificationProxy(callMatcher, sourceLocation: (file, line))
}
/// 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.
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
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.
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
}
public init(numberOfExpectedCalls: Int, compareCallsFunction: (expected: Int, actual: Int) -> Bool) {
self.matchesFunction = {
public init(name: String, numberOfExpectedCalls: Int, compareCallsFunction: (expected: Int, actual: Int) -> Bool) {
self.init(name: name) {
return compareCallsFunction(expected: numberOfExpectedCalls, actual: $0.count)
}
}
public func matches(calls: [StubCall]) -> Bool {
do {
return try matchesFunction(calls)
} catch {
return false
}
return matchesFunction(calls)
}
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)
}
}
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)
}
}

View File

@ -9,7 +9,8 @@
/// Returns a matcher ensuring a call was made **`count`** times.
@warn_unused_result
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.
@ -27,11 +28,11 @@ public func atLeastOnce() -> CallMatcher {
/// Returns a matcher ensuring call was made at least `count` times.
@warn_unused_result
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.
@warn_unused_result
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.
public struct ParameterMatcher<T>: Matchable {
private let matchesFunction: T throws -> Bool
private let matchesFunction: T -> Bool
public init(matchesFunction: T -> Bool = { _ in true }) {
self.matchesFunction = matchesFunction
@ -19,10 +19,6 @@ public struct ParameterMatcher<T>: Matchable {
}
public func matches(input: T) -> Bool {
do {
return try matchesFunction(input)
} catch {
return false
}
return matchesFunction(input)
}
}

View File

@ -84,8 +84,8 @@ public class MockManager {
unverifiedStubCallsIndexes = unverifiedStubCallsIndexes.filter { !indexesToRemove.contains($0) }
if callMatcher.matches(calls) == false {
let description = Description()
MockManager.fail(message: description.description, sourceLocation: sourceLocation)
let message = "Wanted \(callMatcher.name) but \(calls.count == 0 ? "not invoked" : "invoked \(calls.count) times")."
MockManager.fail(message: message, sourceLocation: sourceLocation)
}
return __DoNotUse()
}
@ -106,14 +106,26 @@ public class MockManager {
func verifyNoMoreInteractions(sourceLocation: SourceLocation) {
if unverifiedStubCallsIndexes.isEmpty == false {
let unverifiedCalls = unverifiedStubCallsIndexes.map { stubCalls[$0] }.map { String($0) }.joinWithSeparator(", ")
MockManager.fail(message: "Found unverified call(s): " + unverifiedCalls, sourceLocation: sourceLocation)
let unverifiedCalls = unverifiedStubCallsIndexes.map { stubCalls[$0] }.map { call in
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
private func failAndCrash(message: String, sourceLocation: SourceLocation = (#file, #line)) {
MockManager.fail(message: message, sourceLocation: sourceLocation)
private func failAndCrash(message: String, file: StaticString = #file, line: UInt = #line) {
MockManager.fail(message: message, sourceLocation: (file, line))
fatalError(message)
}
}

View File

@ -8,11 +8,23 @@
public protocol StubCall {
var method: String { get }
var parametersAsString: String { get }
}
public struct ConcreteStubCall<IN>: StubCall {
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) {
self.method = method

View File

@ -64,19 +64,4 @@ class CuckooFunctionsTest: XCTestCase {
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() {
let matcher = CallMatcher { ($0.first?.method ?? "") == "A"}
let matcher = CallMatcher(name: "") { ($0.first?.method ?? "") == "A"}
let nonMatchingCalls = [ConcreteStubCall(method: "B", parameters: Void()) as StubCall]
XCTAssertTrue(matcher.matches(call))

View File

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