Add support for mocking properties. Change placement of file and line parameters.

This commit is contained in:
Tadeas Kriz 2016-02-11 22:50:35 +01:00
parent 3b47e1e16a
commit 2d1cd2b11b
12 changed files with 224 additions and 25 deletions

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Cuckoo"
s.version = "0.1.1"
s.version = "0.3.0"
s.summary = "Cuckoo - first boilerplate-free Swift mocking framework."
s.description = <<-DESC
Cuckoo is a mocking framework with an easy to use API (inspired by Mockito).

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
181C6C2A1C6A67B5009A8CA7 /* TestedClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181C6C281C6A6591009A8CA7 /* TestedClass.swift */; };
181F419F1C46C6B3005BAB70 /* StubbingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181F419E1C46C6B3005BAB70 /* StubbingFunctions.swift */; };
181F41A11C46C6E7005BAB70 /* Verification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181F41A01C46C6E7005BAB70 /* Verification.swift */; };
181F41A31C46C716005BAB70 /* VerificationFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181F41A21C46C716005BAB70 /* VerificationFunctions.swift */; };
@ -36,6 +37,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
181C6C281C6A6591009A8CA7 /* TestedClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestedClass.swift; sourceTree = "<group>"; };
181F419E1C46C6B3005BAB70 /* StubbingFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StubbingFunctions.swift; sourceTree = "<group>"; };
181F41A01C46C6E7005BAB70 /* Verification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Verification.swift; sourceTree = "<group>"; };
181F41A21C46C716005BAB70 /* VerificationFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationFunctions.swift; sourceTree = "<group>"; };
@ -121,6 +123,7 @@
183D040A1C4691C600EBAEF3 /* CuckooAPITest.swift */,
183D040C1C4691C600EBAEF3 /* Info.plist */,
1894D0F01C4D09AB00879512 /* TestedProtocol.swift */,
181C6C281C6A6591009A8CA7 /* TestedClass.swift */,
);
path = Tests;
sourceTree = "<group>";
@ -232,7 +235,7 @@
/* Begin PBXShellScriptBuildPhase section */
1894D0F31C4D119A00879512 /* Generate mocks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
buildActionMask = 8;
files = (
);
inputPaths = (
@ -240,9 +243,9 @@
name = "Generate mocks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
shellScript = "# Find the installed Cuckoo version\nCUCKOO_VERSION=\"0.1.0\"\n\n# Used to ignore Xcode's environment\nalias run=\"env -i PATH=$PATH, HOME=$HOME\"\n\ncuckoo runtime --check $CUCKOO_VERSION\ncuckooReturn=$?\n\nif [ $cuckooReturn == 1 ]; then\n# Update local brew repository and upgrade to latest Cuckoo generator\nrun brew update --verbose\nrun brew upgrade --verbose SwiftKit/cuckoo/cuckoo\nelif [ $cuckooReturn == 127 ]; then\n# Update local brew repository and install latest Cuckoo generator\nrun brew install --verbose SwiftKit/cuckoo/cuckoo\nfi\n\ncuckoo generate --runtime $CUCKOO_VERSION --output \"$PROJECT_DIR/Tests/GeneratedMocks.swift\" \"$PROJECT_DIR/Tests/TestedProtocol.swift\"";
shellScript = "# Find the installed Cuckoo version\nCUCKOO_VERSION=\"0.1.0\"\n\n# Used to ignore Xcode's environment\nalias run=\"env -i PATH=$PATH, HOME=$HOME\"\n\ncuckoo runtime --check $CUCKOO_VERSION\ncuckooReturn=$?\n\nif [ $cuckooReturn == 1 ]; then\n# Update local brew repository and upgrade to latest Cuckoo generator\nrun brew update --verbose\nrun brew upgrade --verbose SwiftKit/cuckoo/cuckoo\nelif [ $cuckooReturn == 127 ]; then\n# Update local brew repository and install latest Cuckoo generator\nrun brew install --verbose SwiftKit/cuckoo/cuckoo\nfi\n\ncuckoo generate --runtime $CUCKOO_VERSION --output \"$PROJECT_DIR/Tests/GeneratedMocks.swift\" \"$PROJECT_DIR/Tests/TestedProtocol.swift\" \"$PROJECT_DIR/Tests/TestedClass.swift\"";
};
/* End PBXShellScriptBuildPhase section */
@ -272,6 +275,7 @@
1894D0F21C4D09CF00879512 /* TestedProtocol.swift in Sources */,
1894D0F71C4D127D00879512 /* GeneratedMocks.swift in Sources */,
1890298B1C4C318A002FF826 /* CuckooAPITest.swift in Sources */,
181C6C2A1C6A67B5009A8CA7 /* TestedClass.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -14,6 +14,14 @@ enum ReturnValueOrError {
case Error(ErrorType)
}
func getterName(property: String) -> String {
return property + "#get"
}
func setterName(property: String) -> String {
return property + "#set"
}
public class MockManager<STUBBING: StubbingProxy, VERIFICATION: VerificationProxy> {
private var stubs: [String: [Stub]] = [:]
private var stubCalls: [StubCall] = []
@ -61,7 +69,7 @@ public class MockManager<STUBBING: StubbingProxy, VERIFICATION: VerificationProx
stubs[stub.name]?.insert(stub, atIndex: 0)
}
private func verify(method: String, file: String, line: UInt, callMatcher: AnyMatcher<StubCall>, verificationMatcher: AnyMatcher<[StubCall]>) {
private func verify(method: String, sourceLocation: SourceLocation, callMatcher: AnyMatcher<StubCall>, verificationMatcher: AnyMatcher<[StubCall]>) {
let calls = stubCalls.filter(callMatcher.matches)
if verificationMatcher.matches(calls) == false {
@ -72,7 +80,7 @@ public class MockManager<STUBBING: StubbingProxy, VERIFICATION: VerificationProx
.appendText(", but ");
verificationMatcher.describeMismatch(calls, to: description);
XCTFail(description.description, file: file, line: line)
XCTFail(description.description, file: sourceLocation.file, line: sourceLocation.line)
}
}
@ -88,13 +96,21 @@ extension MockManager {
extension MockManager {
public func getVerificationProxy(matcher: AnyMatcher<[StubCall]>) -> VERIFICATION {
return VERIFICATION(handler: VerificationHandler(matcher: matcher, verifyCall: verify))
public func getVerificationProxy(matcher: AnyMatcher<[StubCall]>, sourceLocation: SourceLocation) -> VERIFICATION {
return VERIFICATION(handler: VerificationHandler(matcher: matcher, sourceLocation: sourceLocation, verifyCall: verify))
}
}
public extension MockManager {
public func getter<T>(property: String, original: (Void -> T)? = nil) -> (Void -> T) {
return call(getterName(property), original: original)
}
public func setter<T>(property: String, value: T, original: (T -> Void)? = nil) -> (T -> Void) {
return call(setterName(property), parameters: value, original: original)
}
public func call<OUT>(method: String, original: (Void -> OUT)? = nil) -> Void -> OUT {
return doCall(method, parameters: Void(), original: original)
}

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>0.3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@ -43,9 +43,41 @@ public struct ToBeStubbedThrowingFunction<IN, OUT> {
}
}
public struct ToBeStubbedReadOnlyProperty<T> {
let handler: StubbingHandler
let name: String
public var get: ToBeStubbedFunction<Void, T> {
return ToBeStubbedFunction(handler: handler, name: getterName(name), parameterMatchers: [])
}
}
public struct ToBeStubbedProperty<T> {
let handler: StubbingHandler
let name: String
public var get: ToBeStubbedFunction<Void, T> {
return ToBeStubbedFunction(handler: handler, name: getterName(name), parameterMatchers: [])
}
public func set<M: Matchable where M.MatchedType == T>(matcher: M) -> ToBeStubbedFunction<T, Void> {
return ToBeStubbedFunction(handler: handler, name: setterName(name), parameterMatchers: [matcher.matcher])
}
}
public struct StubbingHandler {
let createNewStub: Stub -> ()
public func stubProperty<T>(property: String) -> ToBeStubbedProperty<T> {
return ToBeStubbedProperty(handler: self, name: property)
}
public func stubReadOnlyProperty<T>(property: String) -> ToBeStubbedReadOnlyProperty<T> {
return ToBeStubbedReadOnlyProperty(handler: self, name: property)
}
public func stub<OUT>(method: String) -> ToBeStubbedFunction<Void, OUT> {
return stub(method, parameterMatchers: [] as [AnyMatcher<Void>])
}

View File

@ -59,3 +59,8 @@ public func markerFunction<IN, OUT>(input: IN.Type = IN.self, _ output: OUT.Type
return OUT.self as! OUT
}
}
public struct SourceLocation {
let file: String
let line: UInt
}

View File

@ -12,18 +12,27 @@ public protocol VerificationProxy {
public struct VerificationHandler {
let matcher: AnyMatcher<[StubCall]>
let verifyCall: (method: String, file: String, line: UInt, callMatcher: AnyMatcher<StubCall>, verificationMatcher: AnyMatcher<[StubCall]>) -> ()
let sourceLocation: SourceLocation
let verifyCall: (method: String, sourceLocation: SourceLocation, callMatcher: AnyMatcher<StubCall>, verificationMatcher: AnyMatcher<[StubCall]>) -> ()
public func verify<OUT>(method: String, file: String, line: UInt) -> __DoNotUse<OUT> {
return verify(method, file: file, line: line, parameterMatchers: [] as [AnyMatcher<Void>])
public func verifyProperty<T>(property: String) -> VerifyProperty<T> {
return VerifyProperty(name: property, handler: self)
}
public func verify<IN, OUT>(method: String, file: String, line: UInt, parameterMatchers: [AnyMatcher<IN>]) -> __DoNotUse<OUT> {
return verify(method, file: file, line: line, callMatcher: callMatcher(method, parameterMatchers: parameterMatchers))
public func verifyReadOnlyProperty<T>(property: String) -> VerifyReadOnlyProperty<T> {
return VerifyReadOnlyProperty(name: property, handler: self)
}
public func verify<OUT>(method: String, file: String, line: UInt, callMatcher: AnyMatcher<StubCall>) -> __DoNotUse<OUT> {
verifyCall(method: method, file: file, line: line, callMatcher: callMatcher, verificationMatcher: matcher)
public func verify<OUT>(method: String) -> __DoNotUse<OUT> {
return verify(method, parameterMatchers: [] as [AnyMatcher<Void>])
}
public func verify<IN, OUT>(method: String, parameterMatchers: [AnyMatcher<IN>]) -> __DoNotUse<OUT> {
return verify(method, callMatcher: callMatcher(method, parameterMatchers: parameterMatchers))
}
public func verify<OUT>(method: String, callMatcher: AnyMatcher<StubCall>) -> __DoNotUse<OUT> {
verifyCall(method: method, sourceLocation: sourceLocation, callMatcher: callMatcher, verificationMatcher: matcher)
return __DoNotUse()
}
}
@ -31,6 +40,29 @@ public struct VerificationHandler {
/// Marker struct for use as a return type in verification.
public struct __DoNotUse<T> { }
public struct VerifyReadOnlyProperty<T> {
let name: String
let handler: VerificationHandler
public var get: __DoNotUse<T> {
return handler.verify(getterName(name))
}
}
public struct VerifyProperty<T> {
let name: String
let handler: VerificationHandler
public var get: __DoNotUse<T> {
return handler.verify(getterName(name))
}
public func set<M: Matchable where M.MatchedType == T>(matcher: M) -> __DoNotUse<Void> {
return handler.verify(setterName(name), parameterMatchers: [matcher.matcher])
}
}
public func parameterMatcher<IN, PARAM, M: Matcher where M.MatchedType == PARAM>(matcher: M, mapping: IN -> PARAM) -> AnyMatcher<IN> {
let function: IN -> Bool = {
return matcher.matches(mapping($0))

View File

@ -7,11 +7,11 @@
//
@warn_unused_result
public func verify<M: Mock>(mock: M) -> M.Verification {
return verify(mock, times(1))
public func verify<M: Mock>(mock: M, file: String = __FILE__, line: UInt = __LINE__) -> M.Verification {
return verify(mock, times(1), file: file, line: line)
}
@warn_unused_result
public func verify<M: Mock>(mock: M, _ matcher: AnyMatcher<[StubCall]>) -> M.Verification {
return mock.manager.getVerificationProxy(matcher)
public func verify<M: Mock>(mock: M, _ matcher: AnyMatcher<[StubCall]>, file: String = __FILE__, line: UInt = __LINE__) -> M.Verification {
return mock.manager.getVerificationProxy(matcher, sourceLocation: SourceLocation(file: file, line: line))
}

View File

@ -21,7 +21,7 @@ class MockeryAPITest: XCTestCase {
super.tearDown()
}
func testExample() {
func testProtocol() {
enum TestError: ErrorType {
case Unknown
@ -32,6 +32,12 @@ class MockeryAPITest: XCTestCase {
// FIXME Should be fatalError when method was not throwing
stub(mock) { mock in
when(mock.readOnlyProperty.get).thenReturn("properties!")
when(mock.readWriteProperty.get).thenReturn(10)
when(mock.readWriteProperty.set(anyInt())).then {
print($0)
}
when(mock.noParameter()).thenReturn()
when(mock.countCharacters("hello")).thenReturn(1000)
when(mock.withReturn()).thenReturn("hello world!")
@ -42,6 +48,11 @@ class MockeryAPITest: XCTestCase {
}
}
XCTAssertEqual(mock.readOnlyProperty, "properties!")
XCTAssertEqual(mock.readWriteProperty, 10)
mock.readWriteProperty = 400
XCTAssertEqual(mock.readWriteProperty, 10)
mock.noParameter()
XCTAssertEqual(mock.countCharacters("hello"), 1000)
@ -54,12 +65,62 @@ class MockeryAPITest: XCTestCase {
}
XCTAssertEqual(helloWorld, "hello world")
verify(mock).readOnlyProperty.get
verify(mock, times(2)).readWriteProperty.get
verify(mock).readWriteProperty.set(400)
verify(mock).noParameter()
verify(mock).countCharacters(eq("hello"))
verify(mock).withReturn()
verify(mock, never()).withThrows()
}
func testClass() {
enum TestError: ErrorType {
case Unknown
}
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.readOnlyProperty.get).thenReturn("properties!")
when(mock.readWriteProperty.get).thenReturn(10)
when(mock.readWriteProperty.set(anyInt())).then {
print($0)
}
when(mock.noParameter()).thenReturn()
when(mock.countCharacters("hello")).thenReturn(1000)
when(mock.withReturn()).thenReturn("hello world!")
when(mock.withThrows()).thenThrow(TestError.Unknown)
when(mock.withNoescape("hello", closure: anyClosure())).then {
$1($0 + " world")
}
}
XCTAssertEqual(mock.readOnlyProperty, "properties!")
XCTAssertEqual(mock.readWriteProperty, 10)
mock.readWriteProperty = 400
XCTAssertEqual(mock.readWriteProperty, 10)
mock.noParameter()
XCTAssertEqual(mock.countCharacters("hello"), 1000)
XCTAssertEqual(mock.withReturn(), "hello world!")
var helloWorld: String = ""
mock.withNoescape("hello") {
helloWorld = $0
}
XCTAssertEqual(helloWorld, "hello world")
verify(mock).readOnlyProperty.get
verify(mock, times(2)).readWriteProperty.get
verify(mock).readWriteProperty.set(400)
verify(mock).noParameter()
verify(mock).countCharacters(eq("hello"))
verify(mock).withReturn()
verify(mock, never()).withThrows()
}

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>0.3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

45
Tests/TestedClass.swift Normal file
View File

@ -0,0 +1,45 @@
//
// TestedClass.swift
// Cuckoo
//
// Created by Tadeas Kriz on 09/02/16.
// Copyright © 2016 Brightify. All rights reserved.
//
class TestedClass {
let constant: Float = 0.0
var readOnlyProperty: String {
return ""
}
lazy var readWriteProperty: Int = 0
func noParameter() {
}
func countCharacters(test: String) -> Int {
return test.characters.count
}
func withReturn() -> String {
return "yello world"
}
func withThrows() throws {
}
func withClosure(closure: String -> Int) {
closure("hello")
}
func withMultipleParameters(a: String, b: Int, c: Float) {
}
func withNoescape(a: String, @noescape closure: String -> Void) {
closure(a)
}
}

View File

@ -7,6 +7,10 @@
//
protocol TestedProtocol {
var readOnlyProperty: String { get }
var readWriteProperty: Int { get set }
func noParameter()
func countCharacters(test: String) -> Int