Skip to content

Commit

Permalink
Add support for mocking properties. Change placement of file and line…
Browse files Browse the repository at this point in the history
… parameters.
  • Loading branch information
TadeasKriz committed Feb 11, 2016
1 parent 3b47e1e commit 2d1cd2b
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Cuckoo.podspec
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
10 changes: 7 additions & 3 deletions Cuckoo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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>"; };
Expand Down Expand Up @@ -121,6 +123,7 @@
183D040A1C4691C600EBAEF3 /* CuckooAPITest.swift */,
183D040C1C4691C600EBAEF3 /* Info.plist */,
1894D0F01C4D09AB00879512 /* TestedProtocol.swift */,
181C6C281C6A6591009A8CA7 /* TestedClass.swift */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -232,17 +235,17 @@
/* Begin PBXShellScriptBuildPhase section */
1894D0F31C4D119A00879512 /* Generate mocks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
buildActionMask = 8;
files = (
);
inputPaths = (
);
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 */

Expand Down Expand Up @@ -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;
};
Expand Down
26 changes: 21 additions & 5 deletions Source/Cuckoo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = []
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}

}
Expand All @@ -88,21 +96,29 @@ 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)
}

public func call<IN, OUT>(method: String, parameters: IN, original: (IN -> OUT)? = nil) -> IN -> OUT {
return doCall(method, parameters: parameters, original: original)
}

public func callThrows<OUT>(method: String, original: (Void throws -> OUT)? = nil) -> Void throws -> OUT {
return doCallThrows(method, parameters: Void(), original: original)
}
Expand Down
2 changes: 1 addition & 1 deletion Source/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
32 changes: 32 additions & 0 deletions Source/Stubbing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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>])
}
Expand Down
5 changes: 5 additions & 0 deletions Source/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ public func markerFunction<IN, OUT>(input: IN.Type = IN.self, _ output: OUT.Type
// Will never be called, but Swift cannot infer the type without it
return OUT.self as! OUT
}
}

public struct SourceLocation {
let file: String
let line: UInt
}
46 changes: 39 additions & 7 deletions Source/Verification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,57 @@ 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 verifyReadOnlyProperty<T>(property: String) -> VerifyReadOnlyProperty<T> {
return VerifyReadOnlyProperty(name: property, handler: self)
}

public func verify<OUT>(method: String) -> __DoNotUse<OUT> {
return verify(method, parameterMatchers: [] as [AnyMatcher<Void>])
}

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 verify<IN, OUT>(method: String, parameterMatchers: [AnyMatcher<IN>]) -> __DoNotUse<OUT> {
return verify(method, callMatcher: callMatcher(method, parameterMatchers: parameterMatchers))
}

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, callMatcher: AnyMatcher<StubCall>) -> __DoNotUse<OUT> {
verifyCall(method: method, sourceLocation: sourceLocation, callMatcher: callMatcher, verificationMatcher: matcher)
return __DoNotUse()
}
}

/// 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))
Expand Down
8 changes: 4 additions & 4 deletions Source/VerificationFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
67 changes: 64 additions & 3 deletions Tests/CuckooAPITest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class MockeryAPITest: XCTestCase {
super.tearDown()
}

func testExample() {
func testProtocol() {

enum TestError: ErrorType {
case Unknown
Expand All @@ -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!")
Expand All @@ -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)
Expand All @@ -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()

}
Expand Down
2 changes: 1 addition & 1 deletion Tests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit 2d1cd2b

Please sign in to comment.