Compare commits

...

22 Commits

Author SHA1 Message Date
Matyáš Kříž 5b1ed26237 Address PR comments. 2019-03-27 13:20:43 +01:00
Matyáš Kříž fb5eb7936a Make `Optional` tests functional. 2019-03-27 12:08:56 +01:00
Matyáš Kříž 55b7deaf9e Add throwing and rethrowing stub functionality. 2019-03-27 12:00:37 +01:00
Matyáš Kříž 28de6c1d7d Make closures with no parameters work. 2019-03-27 11:57:35 +01:00
Matyáš Kříž 3cbae5ed2c Add possibility to mock functions with closures containing up to 7 parameters. 2019-03-27 11:57:14 +01:00
Matyáš Kříž 76f85110ac Fix optional closure matching. 2019-03-27 10:35:17 +01:00
Matyáš Kříž e3ac95f5f0 After-rebase fixes. 2019-03-25 13:19:31 +01:00
Matyáš Kříž b73debd32b Finish generic where clause functionality. 2019-03-25 12:57:16 +01:00
Matyáš Kříž a2f9e68e72 Add support for inheritance generic parameters in methods. 2019-03-25 12:54:45 +01:00
Matyáš Kříž 807dc8953b Move collision classes to different file, so that they don’t get mocked. 2019-03-25 12:49:35 +01:00
Matyáš Kříž f236c5e3f6 Remove unnecessary file. 2019-03-25 12:48:58 +01:00
Matyáš Kříž e078d4db98 Add documentation. 2019-03-25 12:48:58 +01:00
Matyáš Kříž 9180694e93 Fix tests. 2019-03-25 12:48:58 +01:00
Matyáš Kříž 335a2175c1 Add some basic tests for generic class and generic protocol mocks. 2019-03-25 12:48:58 +01:00
Matyáš Kříž a7a779c9e2 Add generic protocol capability. 2019-03-25 12:48:58 +01:00
Matyáš Kříž 52a69196dc Add generic protocol parsing and generation.
It doesn’t compile yet, though.
2019-03-25 12:48:58 +01:00
Matyáš Kříž ea9bd46c23 Add generic class support. 2019-03-25 12:46:04 +01:00
Tadeas Kriz 4b403883ea Fix not being able to put Optional into functions accepting Optionals. 2019-03-20 19:17:55 +01:00
Matyáš Kříž 417075a94a WIP allow non-optional values to be passed as matchers for optionals. 2019-03-19 12:58:21 +01:00
Matyáš Kříž ba434e6e91 Add support for inout method parameters. 2019-03-12 17:44:32 +01:00
Matyáš Kříž 9e965f55e2 Ignore everything final. 2019-03-12 14:09:24 +01:00
Matyáš Kříž 0944bca05b Generate public variables and functions in public classes. 2019-03-11 11:17:45 +01:00
45 changed files with 1906 additions and 307 deletions

View File

@ -141,6 +141,17 @@
58C54612D0DB47A284F29AD5 /* ExcludedStubTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C54B2B810BC9284205CB16 /* ExcludedStubTest.swift */; };
58C54C0D4A65BCDC47704C0B /* ExcludedTestClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C54D12B64AF3169FC8A1A3 /* ExcludedTestClass.swift */; };
58C54E2CB6689135915CFC3E /* ExcludedStubTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C54B2B810BC9284205CB16 /* ExcludedStubTest.swift */; };
680894BF21ABFDCB00C8D2EF /* GenericClassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680894BE21ABFDCB00C8D2EF /* GenericClassTest.swift */; };
680894C021ABFDCB00C8D2EF /* GenericClassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680894BE21ABFDCB00C8D2EF /* GenericClassTest.swift */; };
680F09C92202F46A005F5C1A /* CollisionClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680F09C82202F46A005F5C1A /* CollisionClasses.swift */; };
680F09CA2202F46A005F5C1A /* CollisionClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680F09C82202F46A005F5C1A /* CollisionClasses.swift */; };
6861B32921A31DAC002EC2DA /* GenericClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861B30B21A31DA2002EC2DA /* GenericClass.swift */; };
6861B32A21A31DAD002EC2DA /* GenericClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861B30B21A31DA2002EC2DA /* GenericClass.swift */; };
6861B32C21A32279002EC2DA /* GenericProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861B32B21A32279002EC2DA /* GenericProtocol.swift */; };
6861B32D21A32279002EC2DA /* GenericProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861B32B21A32279002EC2DA /* GenericProtocol.swift */; };
6896352E21AC5A4700B25D47 /* GenericProtocolTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6896352D21AC5A4700B25D47 /* GenericProtocolTest.swift */; };
6896352F21AC5A4700B25D47 /* GenericProtocolTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6896352D21AC5A4700B25D47 /* GenericProtocolTest.swift */; };
68E9EAE6224B9F34000DBD29 /* StubFunctionThenThrowingTrait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E9EAE5224B9F34000DBD29 /* StubFunctionThenThrowingTrait.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -242,6 +253,20 @@
remoteGlobalIDString = "FileKit::FileKit::Product";
remoteInfo = FileKit;
};
6861B32521A31DA3002EC2DA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 18E2A3401FBB3FDD0058FEC5 /* CuckooGenerator.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = "SourceKitten::Clang_C::Product";
remoteInfo = Clang_C;
};
6861B32721A31DA3002EC2DA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 18E2A3401FBB3FDD0058FEC5 /* CuckooGenerator.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = "SourceKitten::SourceKit::Product";
remoteInfo = SourceKit;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@ -331,6 +356,12 @@
18E2A3971FBB43E60058FEC5 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = "<group>"; };
58C54B2B810BC9284205CB16 /* ExcludedStubTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExcludedStubTest.swift; sourceTree = "<group>"; };
58C54D12B64AF3169FC8A1A3 /* ExcludedTestClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExcludedTestClass.swift; sourceTree = "<group>"; };
680894BE21ABFDCB00C8D2EF /* GenericClassTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericClassTest.swift; sourceTree = "<group>"; };
680F09C82202F46A005F5C1A /* CollisionClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollisionClasses.swift; sourceTree = "<group>"; };
6861B30B21A31DA2002EC2DA /* GenericClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericClass.swift; sourceTree = "<group>"; };
6861B32B21A32279002EC2DA /* GenericProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericProtocol.swift; sourceTree = "<group>"; };
6896352D21AC5A4700B25D47 /* GenericProtocolTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericProtocolTest.swift; sourceTree = "<group>"; };
68E9EAE5224B9F34000DBD29 /* StubFunctionThenThrowingTrait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubFunctionThenThrowingTrait.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -464,6 +495,7 @@
18E2A2521FBB1C330058FEC5 /* StubFunctionThenReturnTrait.swift */,
18E2A2531FBB1C330058FEC5 /* StubFunctionThenThrowTrait.swift */,
18E2A2541FBB1C330058FEC5 /* StubFunctionThenTrait.swift */,
68E9EAE5224B9F34000DBD29 /* StubFunctionThenThrowingTrait.swift */,
);
path = Trait;
sourceTree = "<group>";
@ -500,6 +532,7 @@
isa = PBXGroup;
children = (
18E2A2601FBB1C330058FEC5 /* ClassTest.swift */,
680894BE21ABFDCB00C8D2EF /* GenericClassTest.swift */,
18E2A2611FBB1C330058FEC5 /* CuckooFunctionsTest.swift */,
18E2A2621FBB1C330058FEC5 /* DefaultValueRegistryTest.swift */,
18E2A2631FBB1C330058FEC5 /* FailTest.swift */,
@ -507,6 +540,7 @@
18E2A2661FBB1C330058FEC5 /* Info.plist */,
18E2A2671FBB1C330058FEC5 /* Matching */,
18E2A26D1FBB1C330058FEC5 /* ProtocolTest.swift */,
6896352D21AC5A4700B25D47 /* GenericProtocolTest.swift */,
18E2A26E1FBB1C330058FEC5 /* Source */,
18E2A2771FBB1C330058FEC5 /* Stubbing */,
18E2A27D1FBB1C330058FEC5 /* StubTest.swift */,
@ -550,6 +584,9 @@
18E2A2751FBB1C330058FEC5 /* TestedSubProtocol.swift */,
18E2A2761FBB1C330058FEC5 /* UnicodeTestProtocol.swift */,
58C54D12B64AF3169FC8A1A3 /* ExcludedTestClass.swift */,
6861B30B21A31DA2002EC2DA /* GenericClass.swift */,
6861B32B21A32279002EC2DA /* GenericProtocol.swift */,
680F09C82202F46A005F5C1A /* CollisionClasses.swift */,
);
path = Source;
sourceTree = "<group>";
@ -615,12 +652,14 @@
isa = PBXGroup;
children = (
18E2A36D1FBB3FDD0058FEC5 /* CYaml.framework */,
6861B32621A31DA3002EC2DA /* Clang_C.framework */,
18E2A35F1FBB3FDD0058FEC5 /* Commandant.framework */,
18E2A35D1FBB3FDD0058FEC5 /* CuckooGeneratorFramework.framework */,
18E2A3711FBB3FDD0058FEC5 /* FileKit.framework */,
18E2A3651FBB3FDD0058FEC5 /* PathKit.framework */,
18E2A3611FBB3FDD0058FEC5 /* Result.framework */,
18E2A36F1FBB3FDD0058FEC5 /* SWXMLHash.framework */,
6861B32821A31DA3002EC2DA /* SourceKit.framework */,
18E2A3691FBB3FDD0058FEC5 /* SourceKittenFramework.framework */,
18E2A3671FBB3FDD0058FEC5 /* Spectre.framework */,
18E2A3631FBB3FDD0058FEC5 /* Stencil.framework */,
@ -862,6 +901,20 @@
remoteRef = 18E2A3701FBB3FDD0058FEC5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
6861B32621A31DA3002EC2DA /* Clang_C.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Clang_C.framework;
remoteRef = 6861B32521A31DA3002EC2DA /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
6861B32821A31DA3002EC2DA /* SourceKit.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = SourceKit.framework;
remoteRef = 6861B32721A31DA3002EC2DA /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
@ -908,7 +961,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "env -i PATH=$PATH PROJECT_DIR=$PROJECT_DIR USE_RUN=$USE_RUN swift \"$PROJECT_DIR/GenerateMocksForTests.swift\"";
shellScript = "GENERATE=1\n\nif [ $GENERATE -ne 0 ]; then\nenv -i PATH=$PATH PROJECT_DIR=$PROJECT_DIR USE_RUN=$USE_RUN swift \"$PROJECT_DIR/GenerateMocksForTests.swift\"\nfi\n";
};
18E2A33F1FBB3FA80058FEC5 /* Generate Mocks */ = {
isa = PBXShellScriptBuildPhase;
@ -946,6 +999,7 @@
18E2A3161FBB234F0058FEC5 /* StubCall.swift in Sources */,
18E2A3051FBB23480058FEC5 /* StubFunctionThenDoNothingTrait.swift in Sources */,
18E2A2FA1FBB23420058FEC5 /* ToBeStubbedProperty.swift in Sources */,
68E9EAE6224B9F34000DBD29 /* StubFunctionThenThrowingTrait.swift in Sources */,
18E2A31B1FBB23550058FEC5 /* Mock.swift in Sources */,
18E2A3071FBB23480058FEC5 /* StubFunctionThenThrowTrait.swift in Sources */,
18E2A3101FBB234B0058FEC5 /* StubThrowingFunction.swift in Sources */,
@ -1030,23 +1084,28 @@
18E2A2EF1FBB22CB0058FEC5 /* StubNoReturnThrowingFunctionTest.swift in Sources */,
18E2A2E31FBB22C40058FEC5 /* TestedSubProtocol.swift in Sources */,
18E2A2DE1FBB22C40058FEC5 /* ClassWithOptionals.swift in Sources */,
6896352F21AC5A4700B25D47 /* GenericProtocolTest.swift in Sources */,
18E2A2D41FBB22BC0058FEC5 /* ProtocolTest.swift in Sources */,
18E2A2ED1FBB22CB0058FEC5 /* StubFunctionTest.swift in Sources */,
18E2A2C51FBB22B60058FEC5 /* ClassTest.swift in Sources */,
18E2A2C81FBB22B60058FEC5 /* FailTest.swift in Sources */,
18E2A2E21FBB22C40058FEC5 /* TestedSubclass.swift in Sources */,
680F09CA2202F46A005F5C1A /* CollisionClasses.swift in Sources */,
18E2A2F11FBB22CB0058FEC5 /* StubTest.swift in Sources */,
18E2A2D31FBB22BC0058FEC5 /* ParameterMatcherTest.swift in Sources */,
18E2A2EE1FBB22CB0058FEC5 /* StubNoReturnFunctionTest.swift in Sources */,
18E2A2C61FBB22B60058FEC5 /* CuckooFunctionsTest.swift in Sources */,
18E2A2D21FBB22BC0058FEC5 /* ParameterMatcherFunctionsTest.swift in Sources */,
18E2A2D11FBB22BC0058FEC5 /* MatchableTest.swift in Sources */,
6861B32A21A31DAD002EC2DA /* GenericClass.swift in Sources */,
18E2A2CF1FBB22BC0058FEC5 /* CallMatcherFunctionsTest.swift in Sources */,
18E2A2C71FBB22B60058FEC5 /* DefaultValueRegistryTest.swift in Sources */,
18E2A2E01FBB22C40058FEC5 /* TestedClass.swift in Sources */,
18E2A2F51FBB22ED0058FEC5 /* ArgumentCaptorTest.swift in Sources */,
680894C021ABFDCB00C8D2EF /* GenericClassTest.swift in Sources */,
58C540B5C5EF0037F77C90B5 /* ExcludedTestClass.swift in Sources */,
58C54612D0DB47A284F29AD5 /* ExcludedStubTest.swift in Sources */,
6861B32D21A32279002EC2DA /* GenericProtocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1067,23 +1126,28 @@
18E2A2E81FBB22CA0058FEC5 /* StubNoReturnThrowingFunctionTest.swift in Sources */,
18E2A2DB1FBB22C30058FEC5 /* TestedSubProtocol.swift in Sources */,
18E2A2D61FBB22C30058FEC5 /* ClassWithOptionals.swift in Sources */,
6896352E21AC5A4700B25D47 /* GenericProtocolTest.swift in Sources */,
18E2A2CE1FBB22BC0058FEC5 /* ProtocolTest.swift in Sources */,
18E2A2E61FBB22CA0058FEC5 /* StubFunctionTest.swift in Sources */,
18E2A2C11FBB22B50058FEC5 /* ClassTest.swift in Sources */,
18E2A2C41FBB22B50058FEC5 /* FailTest.swift in Sources */,
18E2A2DA1FBB22C30058FEC5 /* TestedSubclass.swift in Sources */,
680F09C92202F46A005F5C1A /* CollisionClasses.swift in Sources */,
18E2A2EA1FBB22CA0058FEC5 /* StubTest.swift in Sources */,
18E2A2CD1FBB22BC0058FEC5 /* ParameterMatcherTest.swift in Sources */,
18E2A2E71FBB22CA0058FEC5 /* StubNoReturnFunctionTest.swift in Sources */,
18E2A2C21FBB22B50058FEC5 /* CuckooFunctionsTest.swift in Sources */,
18E2A2CC1FBB22BC0058FEC5 /* ParameterMatcherFunctionsTest.swift in Sources */,
18E2A2CB1FBB22BC0058FEC5 /* MatchableTest.swift in Sources */,
6861B32921A31DAC002EC2DA /* GenericClass.swift in Sources */,
18E2A2C91FBB22BC0058FEC5 /* CallMatcherFunctionsTest.swift in Sources */,
18E2A2C31FBB22B50058FEC5 /* DefaultValueRegistryTest.swift in Sources */,
18E2A2D81FBB22C30058FEC5 /* TestedClass.swift in Sources */,
18E2A2F31FBB22EC0058FEC5 /* ArgumentCaptorTest.swift in Sources */,
680894BF21ABFDCB00C8D2EF /* GenericClassTest.swift in Sources */,
58C54C0D4A65BCDC47704C0B /* ExcludedTestClass.swift in Sources */,
58C54E2CB6689135915CFC3E /* ExcludedStubTest.swift in Sources */,
6861B32C21A32279002EC2DA /* GenericProtocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -35,6 +35,8 @@ let generatorArguments = [
"\(projectDir)/Tests/Source/TestedSubclass.swift",
"\(projectDir)/Tests/Source/TestedSubProtocol.swift",
"\(projectDir)/Tests/Source/ExcludedTestClass.swift",
"\(projectDir)/Tests/Source/GenericClass.swift",
"\(projectDir)/Tests/Source/GenericProtocol.swift",
]
let useRun = Bool(ProcessInfo.processInfo.environment["USE_RUN", default: "false"]) ?? false

View File

@ -28,13 +28,13 @@ public struct Generator {
}
ext.registerFilter("matchableGenericNames") { (value: Any?) in
guard let parameters = value as? [MethodParameter] else { return value }
return self.matchableGenerics(with: parameters)
guard let method = value as? Method else { return value }
return self.matchableGenericTypes(from: method)
}
ext.registerFilter("matchableGenericWhere") { (value: Any?) in
guard let parameters = value as? [MethodParameter] else { return value }
return self.matchableGenerics(where: parameters)
ext.registerFilter("matchableGenericWhereClause") { (value: Any?) in
guard let method = value as? Method else { return value }
return self.matchableGenericsWhereClause(from: method)
}
ext.registerFilter("matchableParameterSignature") { (value: Any?) in
@ -68,18 +68,26 @@ public struct Generator {
return try environment.renderTemplate(string: Templates.mock, context: ["containers": containers, "debug": debug])
}
private func matchableGenerics(with parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "" }
private func matchableGenericTypes(from method: Method) -> String {
guard method.parameters.isEmpty == false else { return "" }
let genericParameters = (1...parameters.count).map { "M\($0): Cuckoo.Matchable" }.joined(separator: ", ")
return "<\(genericParameters)>"
let matchableGenericParameters = method.parameters.enumerated().map { index, parameter -> String in
let type = parameter.isOptional ? "OptionalMatchable" : "Matchable"
return "M\(index + 1): Cuckoo.\(type)"
}
let methodGenericParameters = method.genericParameters.map { $0.description }
return "<\((matchableGenericParameters + methodGenericParameters).joined(separator: ", "))>"
}
private func matchableGenerics(where parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "" }
private func matchableGenericsWhereClause(from method: Method) -> String {
guard method.parameters.isEmpty == false else { return "" }
let whereClause = parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.typeWithoutAttributes))" }.joined(separator: ", ")
return " where \(whereClause)"
let matchableWhereConstraints = method.parameters.enumerated().map { index, parameter -> String in
let type = parameter.isOptional ? "OptionalMatchedType" : "MatchedType"
return "M\(index + 1).\(type) == \(genericSafeType(from: parameter.type.withoutAttributes.unoptionaled.sugarized))"
}
let methodWhereConstraints = method.returnSignature.whereConstraints
return " where \((matchableWhereConstraints + methodWhereConstraints).joined(separator: ", "))"
}
private func matchableParameterSignature(with parameters: [MethodParameter]) -> String {
@ -104,9 +112,9 @@ public struct Generator {
var fullString = ""
for (index, parameter) in parameters.enumerated() {
if parameter.isClosure && !parameter.isEscaping {
let indents = String(repeating: "\t", count: index + 1)
let indents = String(repeating: "\t", count: index)
let tries = (throwing ?? false) ? " try " : " "
fullString += "\(indents)return\(tries)withoutActuallyEscaping(\(parameter.name), do: { (\(parameter.name)) in\n"
fullString += "\(indents)return\(tries)withoutActuallyEscaping(\(parameter.name), do: { (\(parameter.name): @escaping \(parameter.type)) in\n"
}
}
return fullString
@ -116,7 +124,7 @@ public struct Generator {
var fullString = ""
for (index, parameter) in parameters.enumerated() {
if parameter.isClosure && !parameter.isEscaping {
let indents = String(repeating: "\t", count: index + 1)
let indents = String(repeating: "\t", count: index)
fullString += "\(indents)})\n"
}
}

View File

@ -8,24 +8,47 @@
import Foundation
extension Templates {
static let staticGenericParameter = "_CUCKOO$$GENERIC"
static let typeErasureClassName = "DefaultImplCaller"
static let mock = """
{% for container in containers %}
{% for attribute in container.attributes %}
{{ attribute.text }}
{% endfor %}
{{ container.accessibility }} class {{ container.mockName }}: {{ container.name }}, {% if container.isImplementation %}Cuckoo.ClassMock{% else %}Cuckoo.ProtocolMock{% endif %} {
{{ container.accessibility }} typealias MocksType = {{ container.name }}
{{ container.accessibility }} class {{ container.mockName }}{{ container.genericParameters }}: {{ container.name }}{% if container.isImplementation %}{{ container.genericArguments }}{% endif %}, {% if container.isImplementation %}Cuckoo.ClassMock{% else %}Cuckoo.ProtocolMock{% endif %} {
{% if container.isGeneric and not container.isImplementation %}
{{ container.accessibility }} typealias MocksType = \(typeErasureClassName){{ container.genericArguments }}
{% else %}
{{ container.accessibility }} typealias MocksType = {{ container.name }}{{ container.genericArguments }}
{% endif %}
{{ container.accessibility }} typealias Stubbing = __StubbingProxy_{{ container.name }}
{{ container.accessibility }} typealias Verification = __VerificationProxy_{{ container.name }}
private var __defaultImplStub: {{ container.name }}?
{{ container.accessibility }} let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: {{ container.isImplementation }})
{{ container.accessibility }} func enableDefaultImplementation(_ stub: {{ container.name }}) {
{% if container.isGeneric and not container.isImplementation %}
\(Templates.typeErasure.indented())
private var __defaultImplStub: \(typeErasureClassName){{ container.genericArguments }}?
{{ container.accessibility }} func enableDefaultImplementation<\(staticGenericParameter): {{ container.name }}>(_ stub: \(staticGenericParameter)) where {{ container.genericProtocolIdentity }} {
var mutableStub = stub
__defaultImplStub = \(typeErasureClassName)(from: &mutableStub, keeping: mutableStub)
cuckoo_manager.enableDefaultStubImplementation()
}
{{ container.accessibility }} func enableDefaultImplementation<\(staticGenericParameter): GenericProtocol>(mutating stub: UnsafeMutablePointer<\(staticGenericParameter)>) where {{ container.genericProtocolIdentity }} {
__defaultImplStub = \(typeErasureClassName)(from: stub, keeping: nil)
cuckoo_manager.enableDefaultStubImplementation()
}
{% else %}
private var __defaultImplStub: {{ container.name }}{{ container.genericArguments }}?
{{ container.accessibility }} func enableDefaultImplementation(_ stub: {{ container.name }}{{ container.genericArguments }}) {
__defaultImplStub = stub
cuckoo_manager.enableDefaultStubImplementation()
}
{% endif %}
{% for property in container.properties %}
{% if debug %}
@ -79,7 +102,7 @@ extension Templates {
{% for attribute in method.attributes %}
{{ attribute.text }}
{% endfor %}
{{ method.accessibility }}{% if container.isImplementation and method.isOverriding %} override{% endif %} func {{ method.name }}({{ method.parameterSignature }}) {{ method.returnSignature }} {
{{ method.accessibility }}{% if container.isImplementation and method.isOverriding %} override{% endif %} func {{ method.name }}{{ method.genericParameters }}({{ method.parameterSignature }}) {{ method.returnSignature }} {
{{ method.parameters|openNestedClosure:method.isThrowing }}
return{% if method.isThrowing %} try{% endif %} cuckoo_manager.call{% if method.isThrowing %}Throws{% endif %}("{{method.fullyQualifiedName}}",
parameters: ({{method.parameterNames}}),
@ -98,7 +121,6 @@ extension Templates {
\(Templates.stubbingProxy.indented())
\(Templates.verificationProxy.indented())
}
\(Templates.noImplStub)

View File

@ -7,7 +7,7 @@
extension Templates {
static let noImplStub = """
{{container.accessibility}} class {{ container.name }}Stub: {{ container.name }} {
{{container.accessibility}} class {{ container.name }}Stub{{ container.genericParameters }}: {{ container.name }}{% if container.isImplementation %}{{ container.genericArguments }}{% endif %} {
{% for property in container.properties %}
{{ property.accessibility }}{% if container.@type == "ClassDeclaration" %} override{% endif %} var {{ property.name }}: {{ property.type }} {
get {
@ -28,7 +28,7 @@ extension Templates {
{% endfor %}
{% for method in container.methods %}
{{ method.accessibility }}{% if container.@type == "ClassDeclaration" and method.isOverriding %} override{% endif %} func {{ method.name }}({{ method.parameterSignature }}) {{ method.returnSignature }} {
{{ method.accessibility }}{% if container.@type == "ClassDeclaration" and method.isOverriding %} override{% endif %} func {{ method.name }}{{ method.genericParameters }}({{ method.parameterSignature }}) {{ method.returnSignature }} {{ method.whereClause }} {
return DefaultValueRegistry.defaultValue(for: {{method.returnType|genericSafe}}.self)
}
{% endfor %}

View File

@ -21,7 +21,7 @@ extension Templates {
}
{% endfor %}
{% for method in container.methods %}
func {{method.name}}{{method.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> {{method.stubFunction}}<({{method.inputTypes|genericSafe}}){%if method.returnType != "Void" %}, {{method.returnType|genericSafe}}{%endif%}>{{method.parameters|matchableGenericWhere}} {
func {{method.name}}{{method.self|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> {{method.stubFunction}}<({{method.inputTypes|genericSafe}}){%if method.returnType != "Void" %}, {{method.returnType|genericSafe}}{%endif%}>{{method.self|matchableGenericWhereClause}} {
{{method.parameters|parameterMatchers}}
return .init(stub: cuckoo_manager.createStub(for: {{ container.mockName }}.self, method: "{{method.fullyQualifiedName}}", parameterMatchers: matchers))
}

View File

@ -0,0 +1,49 @@
//
// TypeErasureTemplate.swift
// CuckooGeneratorFramework
//
// Created by Matyáš Kříž on 26/11/2018.
//
import Foundation
extension Templates {
static let typeErasure = """
class \(typeErasureClassName){{ container.genericParameters }}: {{ container.name }} {
private let reference: Any
{% for property in container.properties %}private let _getter_storage$${{ property.name }}: () -> {{ property.type }}{% if not property.isReadOnly %}
private let _setter_storage$${{ property.name }}: ({{ property.type }}) -> Void{% endif %}
var {{ property.name }}: {{ property.type }} {
get { return _getter_storage$${{ property.name }}() }{% if not property.isReadOnly %}
set { _setter_storage$${{ property.name }}(newValue) }{% endif %}
}
{% endfor %}
{# For developers: The `keeping reference: Any?` is necessary because when called from the `enableDefaultImplementation(stub:)` method
instead of `enableDefaultImplementation(mutating:)`, we need to prevent the struct getting deallocated. #}
init<\(staticGenericParameter): {{ container.name }}>(from defaultImpl: UnsafeMutablePointer<\(staticGenericParameter)>, keeping reference: @escaping @autoclosure () -> Any?) where {{ container.genericProtocolIdentity }} {
self.reference = reference
{% for property in container.properties %}_getter_storage$${{ property.name }} = { defaultImpl.pointee.{{ property.name }} }
{% if not property.isReadOnly %}_setter_storage$${{ property.name }} = { defaultImpl.pointee.{{ property.name }} = $0 }{% endif %}
{% endfor %}
{% for method in container.methods %}_storage$${{ method.name }} = defaultImpl.pointee.{{ method.name }}
{% endfor %}
}
{% if container.initializers %}
/// MARK:- ignored required initializers{% endif %}
{% for initializer in container.initializers %}required init({{ initializer.parameterSignature }}) {
fatalError("`DefaultImplCaller` class is only used for calling default implementation and can't be initialized on its own.")
}
{% endfor %}
{% for method in container.methods %}
private let _storage$${{ method.name }}: ({{ method.inputTypes }}) -> {{ method.returnType }}
func {{ method.name }}({{ method.parameterSignature }}) {{ method.returnSignature }} {
return _storage$${{ method.name }}({{ method.parameterNames }})
}
{% endfor %}
}
"""
}

View File

@ -28,7 +28,7 @@ extension Templates {
{% for method in container.methods %}
@discardableResult
func {{method.name}}{{method.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<{{method.returnType|genericSafe}}>{{method.parameters|matchableGenericWhere}} {
func {{method.name}}{{method.self|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<({{method.inputTypes|genericSafe}}), {{method.returnType|genericSafe}}>{{method.self|matchableGenericWhereClause}} {
{{method.parameters|parameterMatchers}}
return cuckoo_manager.verify("{{method.fullyQualifiedName}}", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}

View File

@ -77,74 +77,87 @@ public struct Tokenizer {
return Attribute(kind: kind, text: text)
}
let accessibility = (dictionary[Key.Accessibility.rawValue] as? String).flatMap { Accessibility(rawValue: $0) }
let type = dictionary[Key.TypeName.rawValue] as? String
guard !attributes.map({ $0.kind }).contains(.final) else {
if debugMode {
fputs("Cuckoo: Ignoring mocking of '\(name)' because it's marked `final`.\n", stdout)
}
return nil
}
let accessibility = (dictionary[Key.Accessibility.rawValue] as? String).flatMap { Accessibility(rawValue: $0) } ?? .Internal
let type: WrappableType?
if let stringType = dictionary[Key.TypeName.rawValue] as? String {
type = WrappableType(parsing: stringType)
} else {
type = nil
}
switch kind {
case Kinds.ProtocolDeclaration.rawValue:
let subtokens = tokenize(dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? [])
let initializers = subtokens.only(Initializer.self)
let children = subtokens.noneOf(Initializer.self)
let genericParameters = subtokens.only(GenericParameter.self)
return ProtocolDeclaration(
name: name,
accessibility: accessibility!,
accessibility: accessibility,
range: range!,
nameRange: nameRange!,
bodyRange: bodyRange!,
initializers: initializers,
children: children,
inheritedTypes: tokenizedInheritedTypes,
attributes: attributes)
attributes: attributes,
genericParameters: genericParameters)
case Kinds.ClassDeclaration.rawValue:
guard !attributes.map({ $0.kind }).contains(.final) else {
if debugMode {
fputs("Cuckoo: Ignoring mocking of class \(name) because it's marked `final`.\n", stdout)
}
return nil
}
let subtokens = tokenize(dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? [])
let initializers = subtokens.only(Initializer.self)
let children = subtokens.noneOf(Initializer.self).map { child -> Token in
if var property = child as? InstanceVariable {
var accessibleChild = child as? HasAccessibility & Token
if accessibleChild?.accessibility == .Internal {
accessibleChild?.accessibility = accessibility
}
if var property = accessibleChild as? InstanceVariable {
property.overriding = true
return property
} else {
return child
return accessibleChild ?? child
}
}
let genericParameters = subtokens.only(GenericParameter.self)
return ClassDeclaration(
name: name,
accessibility: accessibility!,
accessibility: accessibility,
range: range!,
nameRange: nameRange!,
bodyRange: bodyRange!,
initializers: initializers,
children: children,
inheritedTypes: tokenizedInheritedTypes,
attributes: attributes)
attributes: attributes,
genericParameters: genericParameters)
case Kinds.ExtensionDeclaration.rawValue:
return ExtensionDeclaration(range: range!)
case Kinds.InstanceVariable.rawValue:
let setterAccessibility = (dictionary[Key.SetterAccessibility.rawValue] as? String).flatMap(Accessibility.init)
if String(source.utf8.dropFirst(range!.startIndex))?.takeUntil(occurence: name)?.trimmed.hasPrefix("let") == true {
return nil
}
if type == nil {
stderrPrint("Type of instance variable \(name) could not be inferred. Please specify it explicitly. (\(file.path ?? ""))")
}
return InstanceVariable(
name: name,
type: type ?? "__UnknownType",
accessibility: accessibility!,
type: type ?? .type("__UnknownType"),
accessibility: accessibility,
setterAccessibility: setterAccessibility,
range: range!,
nameRange: nameRange!,
@ -152,48 +165,58 @@ public struct Tokenizer {
attributes: attributes)
case Kinds.InstanceMethod.rawValue:
let genericParameters = tokenize(dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? []).only(GenericParameter.self)
let parameters = tokenize(methodName: name, parameters: dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? [])
var returnSignature: String
let returnSignature: ReturnSignature
if let bodyRange = bodyRange {
returnSignature = source.utf8[nameRange!.endIndex..<bodyRange.startIndex].takeUntil(occurence: "{")?.trimmed ?? ""
returnSignature = parseReturnSignature(source: source.utf8[nameRange!.endIndex..<bodyRange.startIndex].takeUntil(occurence: "{")?.trimmed ?? "")
} else {
returnSignature = source.utf8[nameRange!.endIndex..<range!.endIndex].trimmed
if returnSignature.isEmpty {
let untilThrows = String(source.utf8.dropFirst(nameRange!.endIndex))?
.takeUntil(occurence: "throws").map { $0 + "throws" }?
.trimmed
if let untilThrows = untilThrows, untilThrows == "throws" || untilThrows == "rethrows" {
returnSignature = "\(untilThrows)"
}
}
}
if returnSignature.isEmpty == false {
returnSignature = " " + returnSignature
returnSignature = parseReturnSignature(source: source.utf8[nameRange!.endIndex..<range!.endIndex].trimmed)
}
// When bodyRange != nil, we need to create .ClassMethod instead of .ProtocolMethod
if let bodyRange = bodyRange {
return ClassMethod(
name: name,
accessibility: accessibility!,
accessibility: accessibility,
returnSignature: returnSignature,
range: range!,
nameRange: nameRange!,
parameters: parameters,
bodyRange: bodyRange,
attributes: attributes)
attributes: attributes,
genericParameters: genericParameters)
} else {
return ProtocolMethod(
name: name,
accessibility: accessibility!,
accessibility: accessibility,
returnSignature: returnSignature,
range: range!,
nameRange: nameRange!,
parameters: parameters,
attributes: attributes)
attributes: attributes,
genericParameters: genericParameters)
}
case Kinds.GenericParameter.rawValue:
return tokenize(parameterLabel: nil, parameter: representable)
case Kinds.AssociatedType.rawValue:
let regex = try! NSRegularExpression(pattern: "\\s*:\\s*([^\\s;\\/]+)")
guard let nameRange = nameRange, let range = range else { return nil }
guard let inheritanceMatch = regex.firstMatch(
in: source,
range: NSMakeRange(range.startIndex, range.endIndex - range.startIndex)) else {
return GenericParameter(name: name, range: range, inheritedType: nil)
}
let inheritanceRange = inheritanceMatch.range(at: 1)
let fromIndex = source.index(source.startIndex, offsetBy: inheritanceRange.location)
let toIndex = source.index(fromIndex, offsetBy: inheritanceRange.length)
let inheritance = String(source[fromIndex..<toIndex])
let fullRange = range.lowerBound..<(range.upperBound + inheritanceMatch.range.length)
return GenericParameter(name: name, range: fullRange, inheritedType: InheritanceDeclaration(name: inheritance))
default:
// Do not log anything, until the parser contains all known cases.
// stderrPrint("Unknown kind. Dictionary: \(dictionary) \(file.path ?? "")")
@ -216,12 +239,12 @@ public struct Tokenizer {
return kind == Kinds.MethodParameter.rawValue
}
return zip(parameterLabels, filteredParameters).compactMap(tokenize)
return zip(parameterLabels, filteredParameters).compactMap { tokenize(parameterLabel: $0, parameter: $1) as? MethodParameter }
}
private func tokenize(parameterLabel: String?, parameter: SourceKitRepresentable) -> MethodParameter? {
private func tokenize(parameterLabel: String?, parameter: SourceKitRepresentable) -> Token? {
guard let dictionary = parameter as? [String: SourceKitRepresentable] else { return nil }
let name = dictionary[Key.Name.rawValue] as? String ?? Tokenizer.nameNotSet
let kind = dictionary[Key.Kind.rawValue] as? String ?? Tokenizer.unknownType
let range = extractRange(from: dictionary, offset: .Offset, length: .Length)
@ -230,7 +253,41 @@ public struct Tokenizer {
switch kind {
case Kinds.MethodParameter.rawValue:
return MethodParameter(label: parameterLabel, name: name, type: type!, range: range!, nameRange: nameRange!)
// separate `inout` from the type and remember that the parameter is inout
let type = type!
// we want to remove `inout` and remember it, but we don't want to affect a potential `inout` closure parameter
let inoutSeparatedType: String
let isInout: Bool
if let inoutRange = type.range(of: "inout ") {
if let closureParenIndex = type.firstIndex(of: "("), closureParenIndex < inoutRange.upperBound {
inoutSeparatedType = type
isInout = false
} else {
var mutableString = type
mutableString.removeSubrange(inoutRange)
inoutSeparatedType = mutableString
isInout = true
}
} else {
inoutSeparatedType = type
isInout = false
}
let wrappableType = WrappableType(parsing: inoutSeparatedType)
return MethodParameter(label: parameterLabel, name: name, type: wrappableType, range: range!, nameRange: nameRange!, isInout: isInout)
case Kinds.GenericParameter.rawValue:
let inheritedTypeElement = (dictionary[Key.InheritedTypes.rawValue] as? [SourceKitRepresentable] ?? []).first
let inheritedType = (inheritedTypeElement as? [String: SourceKitRepresentable] ?? [:])[Key.Name.rawValue] as? String
let inheritanceDeclaration: InheritanceDeclaration?
if let inheritedType = inheritedType {
inheritanceDeclaration = .init(name: inheritedType)
} else {
inheritanceDeclaration = nil
}
return GenericParameter(name: name, range: range!, inheritedType: inheritanceDeclaration)
default:
stderrPrint("Unknown method parameter. Dictionary: \(dictionary) \(file.path ?? "")")
@ -263,12 +320,8 @@ public struct Tokenizer {
rangesToIgnore.filter { $0 ~= result.range.location }.isEmpty
}
.map { result -> Import in
let libraryRange = result.range(at: 1)
let fromIndex = source.index(source.startIndex, offsetBy: libraryRange.location)
let toIndex = source.index(fromIndex, offsetBy: libraryRange.length)
let library = String(source[fromIndex..<toIndex])
let range = result.range.location..<(result.range.location + result.range.length)
print(library)
let library = source.stringMatch(from: result, at: 1)
return Import(range: range, importee: .library(name: library))
}
let components = componentRegex.matches(in: source, range: NSRange(location: 0, length: source.count))
@ -281,7 +334,6 @@ public struct Tokenizer {
let library = source[result.range(at: 2)]
let component = source[result.range(at: 3)]
let range = result.range.location..<(result.range.location + result.range.length)
print(library, component)
return Import(range: range, importee: .component(componentType: componentType, library: library, name: component))
}
@ -290,6 +342,90 @@ public struct Tokenizer {
fatalError("Invalid regex:" + error.description)
}
}
/// - parameter source: A trimmed string containing only the method return signature excluding the trailing brace
/// - returns: tuple containing parsed throwString, returnType, and where constraints
private func parseReturnSignature(source: String) -> ReturnSignature {
var throwString = nil as String?
var returnType = ""
var whereConstraints = [] as [String]
var index = source.startIndex
var parenLevel = 0
var afterArrow = false
parseLoop: while index != source.endIndex {
let character = source[index]
switch character {
case "(", "<", "[":
parenLevel += 1
returnType.append(character)
afterArrow = false
case ")", ">", "]":
parenLevel -= 1
returnType.append(character)
afterArrow = false
case "r" where returnType.isEmpty:
throwString = "rethrows"
index = source.index(index, offsetBy: throwString!.count)
continue
case "t" where returnType.isEmpty:
throwString = "throws"
index = source.index(index, offsetBy: throwString!.count)
continue
case "w" where parenLevel == 0 && !afterArrow:
index = source.index(index, offsetBy: "where".count)
whereConstraints = parseWhereClause(source: source, index: &index)
// the where clause is the last thing in method signature, so we'll just return our parsings
break parseLoop
case "-" where parenLevel == 0:
index = source.index(after: index)
guard source[index] == ">" else { fatalError("Uhh, what.") }
// we will omit the first arrow, so that the generator doesn't have to remove it if it needs the type alone
if !returnType.trimmed.isEmpty {
returnType.append("->")
}
afterArrow = true
default:
returnType.append(character)
afterArrow = false
}
index = source.index(after: index)
}
let trimmedType = returnType.trimmed
let typizedReturnType = trimmedType.isEmpty ? "Void" : trimmedType
return ReturnSignature(throwString: throwString, returnType: WrappableType(parsing: typizedReturnType), whereConstraints: whereConstraints)
}
/// - returns: the where constraints parsed from the where clause
private func parseWhereClause(source: String, index: inout String.Index) -> [String] {
var whereConstraints = [] as [String]
var currentConstraint = ""
var parenLevel = 0
while index != source.endIndex {
let character = source[index]
switch character {
case "(", "<", "[":
parenLevel += 1
case ")", ">", "]":
parenLevel -= 1
case "," where parenLevel == 0:
currentConstraint = currentConstraint.trimmed
whereConstraints.append(currentConstraint)
currentConstraint = ""
default:
currentConstraint.append(character)
}
index = source.index(after: index)
}
if !currentConstraint.isEmpty {
currentConstraint = currentConstraint.trimmed
whereConstraints.append(currentConstraint)
}
return whereConstraints
}
}
extension String {
@ -299,3 +435,22 @@ extension String {
return String(self[fromIndex..<toIndex])
}
}
extension String {
func stringMatch(from match: NSTextCheckingResult, at range: Int = 0) -> String {
let matchRange = match.range(at: range)
let fromIndex = index(startIndex, offsetBy: matchRange.location)
let toIndex = index(fromIndex, offsetBy: matchRange.length)
return String(self[fromIndex..<toIndex])
}
func removing(match: NSTextCheckingResult, at range: Int = 0) -> String {
let matchRange = match.range(at: range)
let fromIndex = index(startIndex, offsetBy: matchRange.location)
let toIndex = index(fromIndex, offsetBy: matchRange.length)
var mutableString = self
mutableString.removeSubrange(fromIndex..<toIndex)
return mutableString
}
}

View File

@ -6,18 +6,18 @@
// Copyright © 2016 Brightify. All rights reserved.
//
public struct ClassDeclaration: ContainerToken {
public let name: String
public let accessibility: Accessibility
public let range: CountableRange<Int>
public let nameRange: CountableRange<Int>
public let bodyRange: CountableRange<Int>
public let initializers: [Initializer]
public let children: [Token]
public struct ClassDeclaration: ContainerToken, HasAccessibility {
public let implementation: Bool = true
public let inheritedTypes: [InheritanceDeclaration]
public let attributes: [Attribute]
public var name: String
public var accessibility: Accessibility
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>
public var bodyRange: CountableRange<Int>
public var initializers: [Initializer]
public var children: [Token]
public var inheritedTypes: [InheritanceDeclaration]
public var attributes: [Attribute]
public var genericParameters: [GenericParameter]
public var hasNoArgInit: Bool {
return initializers.filter { $0.parameters.isEmpty }.isEmpty
}
@ -32,7 +32,8 @@ public struct ClassDeclaration: ContainerToken {
initializers: self.initializers,
children: tokens,
inheritedTypes: self.inheritedTypes,
attributes: self.attributes)
attributes: self.attributes,
genericParameters: self.genericParameters)
}
public func isEqual(to other: Token) -> Bool {

View File

@ -7,14 +7,15 @@
//
public struct ClassMethod: Method {
public let name: String
public let accessibility: Accessibility
public let returnSignature: String
public let range: CountableRange<Int>
public let nameRange: CountableRange<Int>
public let parameters: [MethodParameter]
public let bodyRange: CountableRange<Int>
public let attributes: [Attribute]
public var name: String
public var accessibility: Accessibility
public var returnSignature: ReturnSignature
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>
public var parameters: [MethodParameter]
public var bodyRange: CountableRange<Int>
public var attributes: [Attribute]
public var genericParameters: [GenericParameter]
public var isOptional: Bool {
return false
}

View File

@ -6,7 +6,7 @@
// Copyright © 2016 Brightify. All rights reserved.
//
public protocol ContainerToken: Token {
public protocol ContainerToken: Token, HasAccessibility {
var name: String { get }
var accessibility: Accessibility { get }
var range: CountableRange<Int> { get }
@ -17,6 +17,7 @@ public protocol ContainerToken: Token {
var implementation: Bool { get }
var inheritedTypes: [InheritanceDeclaration] { get }
var attributes: [Attribute] { get }
var genericParameters: [GenericParameter] { get }
}
extension ContainerToken {
@ -33,6 +34,11 @@ extension ContainerToken {
.filter { $0.accessibility.isAccessible && $0.isInit && !$0.isDeinit }
.map { $0.serializeWithType() }
let genericParametersString = genericParameters.map { $0.description }.joined(separator: ", ")
let genericArgumentsString = genericParameters.map { $0.name }.joined(separator: ", ")
let genericProtocolIdentity = genericParameters.map { "\(Templates.staticGenericParameter).\($0.name) == \($0.name)" }.joined(separator: ", ")
let isGeneric = !genericParameters.isEmpty
return [
"name": name,
"accessibility": accessibility.sourceName,
@ -45,6 +51,10 @@ extension ContainerToken {
"mockName": "Mock\(name)",
"inheritedTypes": inheritedTypes,
"attributes": attributes.filter { $0.isSupported },
"isGeneric": isGeneric,
"genericParameters": isGeneric ? "<\(genericParametersString)>" : "",
"genericArguments": isGeneric ? "<\(genericArgumentsString)>" : "",
"genericProtocolIdentity": genericProtocolIdentity,
]
}
}

View File

@ -0,0 +1,31 @@
//
// GenericParameter.swift
// CuckooGeneratorFramework
//
// Created by Matyáš Kříž on 19/11/2018.
//
import Foundation
public struct GenericParameter: Token {
public let name: String
public let range: CountableRange<Int>
public let inheritedType: InheritanceDeclaration?
public var description: String {
let hasInheritedType = inheritedType != nil
return "\(name)\(hasInheritedType ? ": " : "")\(inheritedType?.name ?? "")"
}
public func isEqual(to other: Token) -> Bool {
guard let other = other as? GenericParameter else { return false }
return self.name == other.name && self.range == other.range && self.inheritedType?.name == other.inheritedType?.name
}
public func serialize() -> [String : Any] {
return [
"name": name,
"inheritedType": inheritedType,
]
}
}

View File

@ -0,0 +1,12 @@
//
// HasAccessibility.swift
// CuckooGenerator
//
// Created by Matyáš Kříž on 11/03/2019.
//
import Foundation
public protocol HasAccessibility {
var accessibility: Accessibility { get set }
}

View File

@ -6,16 +6,18 @@
// Copyright © 2016 Brightify. All rights reserved.
//
public struct Initializer: Method {
public let name: String
public let accessibility: Accessibility
public let returnSignature: String
public let range: CountableRange<Int>
public let nameRange: CountableRange<Int>
public let parameters: [MethodParameter]
public let isOverriding: Bool
public let required: Bool
public let attributes: [Attribute]
public struct Initializer: Method, HasAccessibility {
public var name: String
public var accessibility: Accessibility
public var returnType: WrappableType
public var returnSignature: ReturnSignature
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>
public var parameters: [MethodParameter]
public var isOverriding: Bool
public var required: Bool
public var attributes: [Attribute]
public var genericParameters: [GenericParameter]
public var isOptional: Bool {
return false

View File

@ -6,15 +6,15 @@
// Copyright © 2016 Brightify. All rights reserved.
//
public struct InstanceVariable: Token {
public let name: String
public let type: String
public let accessibility: Accessibility
public let setterAccessibility: Accessibility?
public let range: CountableRange<Int>
public let nameRange: CountableRange<Int>
public struct InstanceVariable: Token, HasAccessibility {
public var name: String
public var type: WrappableType
public var accessibility: Accessibility
public var setterAccessibility: Accessibility?
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>
public var overriding: Bool
public let attributes: [Attribute]
public var attributes: [Attribute]
public var readOnly: Bool {
if let setterAccessibility = setterAccessibility {
@ -33,7 +33,7 @@ public struct InstanceVariable: Token {
let readOnlyString = readOnly ? "ReadOnly" : ""
return [
"name": name,
"type": type,
"type": type.sugarized,
"accessibility": accessibility.sourceName,
"isReadOnly": readOnly,
"stubType": overriding ? "ClassToBeStubbed\(readOnlyString)Property" : "ProtocolToBeStubbed\(readOnlyString)Property",

View File

@ -13,4 +13,7 @@ public enum Kinds: String {
case ClassDeclaration = "source.lang.swift.decl.class"
case ExtensionDeclaration = "source.lang.swift.decl.extension"
case InstanceVariable = "source.lang.swift.decl.var.instance"
case GenericParameter = "source.lang.swift.decl.generic_type_param"
case AssociatedType = "source.lang.swift.decl.associatedtype"
case Optional = "source.decl.attribute.optional"
}

View File

@ -6,60 +6,61 @@
// Copyright © 2016 Brightify. All rights reserved.
//
public protocol Method: Token {
public protocol Method: Token, HasAccessibility {
var name: String { get }
var accessibility: Accessibility { get }
var returnSignature: String { get }
var returnSignature: ReturnSignature { get }
var range: CountableRange<Int> { get }
var nameRange: CountableRange<Int> { get }
var parameters: [MethodParameter] { get }
var isOptional: Bool { get }
var isOverriding: Bool { get }
var hasClosureParams: Bool { get }
var hasOptionalParams: Bool { get }
var attributes: [Attribute] { get }
var genericParameters: [GenericParameter] { get }
}
public extension Method {
var rawName: String {
return name.takeUntil(occurence: "(") ?? ""
}
var isInit: Bool {
return rawName == "init"
}
var isDeinit: Bool {
return rawName == "deinit"
}
var fullyQualifiedName: String {
let parameterTypes = parameters.map { $0.type }
let parameterTypes = parameters.map { ($0.isInout ? "inout " : "") + $0.type.sugarized }
let nameParts = name.components(separatedBy: ":")
let lastNamePart = nameParts.last ?? ""
let returnSignatureDescription = returnSignature.description
let returnSignatureString = returnSignatureDescription.isEmpty ? "" : " \(returnSignatureDescription)"
return zip(nameParts.dropLast(), parameterTypes)
.map { $0 + ": " + $1 }
.joined(separator: ", ") + lastNamePart + returnSignature
.joined(separator: ", ") + lastNamePart + returnSignatureString
}
var isThrowing: Bool {
return returnSignature.trimmed.hasPrefix("throws")
return returnSignature.isThrowing || returnSignature.isRethrowing
}
var returnType: String {
if let range = returnSignature.range(of: "->") {
var type = String(returnSignature[range.upperBound...]).trimmed
while type.hasSuffix("?") {
type = "Optional<\(type[..<type.index(before: type.endIndex)])>"
}
return type
} else {
return "Void"
}
var returnType: WrappableType {
return returnSignature.returnType
}
var hasClosureParams: Bool {
return parameters.filter { $0.isClosure }.count > 0
return parameters.contains { $0.isClosure }
}
var hasOptionalParams: Bool {
return parameters.contains { $0.isOptional }
}
public func isEqual(to other: Token) -> Bool {
@ -69,23 +70,24 @@ public extension Method {
public func serialize() -> [String : Any] {
let call = parameters.map {
let referencedName = "\($0.isInout ? "&" : "")\($0.name)"
if let label = $0.label {
return "\(label): \($0.name)"
return "\(label): \(referencedName)"
} else {
return $0.name
return referencedName
}
}.joined(separator: ", ")
let stubFunctionPrefix = isOverriding ? "Class" : "Protocol"
let stubFunction: String
if isThrowing {
if returnType == "Void" {
if returnType.sugarized == "Void" {
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubNoReturnThrowingFunction"
} else {
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubThrowingFunction"
}
} else {
if returnType == "Void" {
if returnType.sugarized == "Void" {
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubNoReturnFunction"
} else {
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubFunction"
@ -94,32 +96,41 @@ public extension Method {
let escapingParameterNames = parameters.map { parameter in
if parameter.isClosure && !parameter.isEscaping {
return "escapingStub(for: \(parameter.name))"
let parameterCount = parameter.closureParamCount
let parameterSignature = parameterCount > 0 ? (1...parameterCount).map { _ in "_" }.joined(separator: ", ") : "()"
return "{ \(parameterSignature) in fatalError(\"This is a stub! It's not supposed to be called!\") }"
} else {
return parameter.name
}
}.joined(separator: ", ")
let genericParametersString = genericParameters.map { $0.description }.joined(separator: ", ")
let isGeneric = !genericParameters.isEmpty
return [
"self": self,
"name": rawName,
"accessibility": accessibility.sourceName,
"returnSignature": returnSignature,
"returnSignature": returnSignature.description,
"parameters": parameters,
"parameterNames": parameters.map { $0.name }.joined(separator: ", "),
"escapingParameterNames": escapingParameterNames,
"isInit": isInit,
"returnType": returnType,
"returnType": returnType.sugarizedExplicitOnly,
"isThrowing": isThrowing,
"fullyQualifiedName": fullyQualifiedName,
"call": call,
"isOverriding": isOverriding,
"parameterSignature": parameters.map { "\($0.labelAndName): \($0.type)" }.joined(separator: ", "),
"parameterSignature": parameters.map { "\($0.labelAndName): \($0.isInout ? "inout " : "")\($0.type)" }.joined(separator: ", "),
"parameterSignatureWithoutNames": parameters.map { "\($0.name): \($0.type)" }.joined(separator: ", "),
"argumentSignature": parameters.map { $0.type.description }.joined(separator: ", "),
"stubFunction": stubFunction,
"inputTypes": parameters.map { $0.typeWithoutAttributes }.joined(separator: ", "),
"isOptional": isOptional,
"hasClosureParams": hasClosureParams,
"hasOptionalParams": hasOptionalParams,
"attributes": attributes.filter { $0.isSupported },
"genericParameters": isGeneric ? "<\(genericParametersString)>" : "",
]
}
}

View File

@ -7,12 +7,13 @@
//
public struct MethodParameter: Token, Equatable {
public let label: String?
public let name: String
public let type: String
public let range: CountableRange<Int>
public let nameRange: CountableRange<Int>
public var label: String?
public var name: String
public var type: WrappableType
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>
public var isInout: Bool
public var labelAndName: String {
if let label = label {
return label != name ? "\(label) \(name)" : name
@ -20,24 +21,53 @@ public struct MethodParameter: Token, Equatable {
return "_ \(name)"
}
}
public var typeWithoutAttributes: String {
return type.replacingOccurrences(of: "@escaping", with: "").replacingOccurrences(of: "@autoclosure", with: "").trimmed
return type.withoutAttributes.sugarized.trimmed
}
public func isEqual(to other: Token) -> Bool {
guard let other = other as? MethodParameter else { return false }
return self.name == other.name && self.type == other.type && self.label == other.label
}
public var isClosure: Bool {
public var isClosure: Bool {
return typeWithoutAttributes.hasPrefix("(") && typeWithoutAttributes.range(of: "->") != nil
}
public var isEscaping: Bool {
return isClosure && (type.hasPrefix("@escaping") || type.hasSuffix(")?"))
public var isOptional: Bool {
return type.isOptional
}
public var closureParamCount: Int {
// make sure that the parameter is a closure and that it's not just an empty `() -> ...` closure
guard isClosure && !"^\\s*\\(\\s*\\)".regexMatches(typeWithoutAttributes) else { return 0 }
var parenLevel = 0
var parameterCount = 1
for character in typeWithoutAttributes {
switch character {
case "(", "<":
parenLevel += 1
case ")", ">":
parenLevel -= 1
case ",":
parameterCount += parenLevel == 1 ? 1 : 0
default:
break
}
if parenLevel == 0 {
break
}
}
return parameterCount
}
public var isEscaping: Bool {
return isClosure && (type.containsAttribute(named: "@escaping") || type.isOptional)
}
public func serialize() -> [String : Any] {
return [
"label": label ?? "",
@ -46,6 +76,7 @@ public struct MethodParameter: Token, Equatable {
"labelAndName": labelAndName,
"typeWithoutAttributes": typeWithoutAttributes,
"isClosure": isClosure,
"isOptional": isOptional,
"isEscaping": isEscaping
]
}

View File

@ -6,17 +6,18 @@
// Copyright © 2016 Brightify. All rights reserved.
//
public struct ProtocolDeclaration: ContainerToken {
public let name: String
public let accessibility: Accessibility
public let range: CountableRange<Int>
public let nameRange: CountableRange<Int>
public let bodyRange: CountableRange<Int>
public let initializers: [Initializer]
public let children: [Token]
public struct ProtocolDeclaration: ContainerToken, HasAccessibility {
public let implementation: Bool = false
public let inheritedTypes: [InheritanceDeclaration]
public let attributes: [Attribute]
public var name: String
public var accessibility: Accessibility
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>
public var bodyRange: CountableRange<Int>
public var initializers: [Initializer]
public var children: [Token]
public var inheritedTypes: [InheritanceDeclaration]
public var attributes: [Attribute]
public var genericParameters: [GenericParameter]
public func replace(children tokens: [Token]) -> ProtocolDeclaration {
return ProtocolDeclaration(
@ -28,7 +29,8 @@ public struct ProtocolDeclaration: ContainerToken {
initializers: self.initializers,
children: tokens,
inheritedTypes: self.inheritedTypes,
attributes: self.attributes)
attributes: self.attributes,
genericParameters: self.genericParameters)
}
public func isEqual(to other: Token) -> Bool {

View File

@ -7,14 +7,15 @@
//
public struct ProtocolMethod: Method {
public let name: String
public let accessibility: Accessibility
public let returnSignature: String
public let range: CountableRange<Int>
public let nameRange: CountableRange<Int>
public let parameters: [MethodParameter]
public let attributes: [Attribute]
public var name: String
public var accessibility: Accessibility
public var returnSignature: ReturnSignature
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>
public var parameters: [MethodParameter]
public var attributes: [Attribute]
public var genericParameters: [GenericParameter]
public var isOptional: Bool {
return attributes.map { $0.kind }.contains(.optional)
}

View File

@ -0,0 +1,33 @@
//
// ReturnSignature.swift
// CuckooGeneratorFramework
//
// Created by Matyáš Kříž on 25/03/2019.
//
import Foundation
public struct ReturnSignature {
var throwString: String?
var returnType: WrappableType
var whereConstraints: [String]
var isThrowing: Bool {
guard let throwString = throwString else { return false }
return throwString.trimmed.hasPrefix("throws")
}
var isRethrowing: Bool {
guard let throwString = throwString else { return false }
return throwString.trimmed.hasPrefix("rethrows")
}
}
extension ReturnSignature: CustomStringConvertible {
public var description: String {
let trimmedReturnType = returnType.sugarizedExplicitOnly.trimmed
let returnString = trimmedReturnType.isEmpty || trimmedReturnType == "Void" ? nil : "-> \(returnType)"
let whereString = whereConstraints.isEmpty ? nil : "where \(whereConstraints.joined(separator: ", "))"
return [throwString, returnString, whereString].compactMap { $0 }.joined(separator: " ")
}
}

View File

@ -0,0 +1,155 @@
//
// WrappableType.swift
// CuckooGeneratorFramework
//
// Created by Matyáš Kříž on 13/03/2019.
//
public enum WrappableType {
indirect case optional(WrappableType)
indirect case implicitlyUnwrappedOptional(WrappableType)
indirect case attributed(WrappableType, attributes: [String])
case type(String)
public var sugarized: String {
switch self {
case .optional(let wrapped):
return "\(wrapped.sugarized)?"
case .implicitlyUnwrappedOptional(let wrapped):
return "\(wrapped.sugarized)!"
case .attributed(let wrapped, let attributes):
return "\(attributes.joined(separator: " ")) \(wrapped.sugarized)"
case .type(let type):
return type
}
}
public var sugarizedExplicitOnly: String {
switch self {
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
return "\(wrapped.sugarizedExplicitOnly)?"
case .attributed(let wrapped, let attributes):
return "\(attributes.joined(separator: " ")) \(wrapped.sugarizedExplicitOnly)"
case .type(let type):
return type
}
}
public var desugarized: String {
switch self {
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
return "Optional<\(wrapped.desugarized)>"
case .attributed(let wrapped, let attributes):
return "\(attributes.joined(separator: " ")) \(wrapped.desugarized)"
case .type(let type):
return type
}
}
public var unoptionaled: WrappableType {
switch self {
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
return wrapped.unoptionaled
case .attributed(let wrapped, let attributes):
return .attributed(wrapped.unoptionaled, attributes: attributes)
case .type:
return self
}
}
public var unwrapped: WrappableType {
switch self {
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
return wrapped
case .attributed(let wrapped, let attributes):
return .attributed(wrapped.unwrapped, attributes: attributes)
case .type:
return self
}
}
public var withoutAttributes: WrappableType {
switch self {
case .optional(let wrapped):
return .optional(wrapped.withoutAttributes)
case .implicitlyUnwrappedOptional(let wrapped):
return .implicitlyUnwrappedOptional(wrapped.withoutAttributes)
case .attributed(let wrapped, let attributes):
return wrapped
case .type(let typeString):
return .type(typeString)
}
}
public var isOptional: Bool {
switch self {
case .optional, .implicitlyUnwrappedOptional:
return true
case .attributed(let wrapped, let attributes):
return wrapped.isOptional
case .type:
return false
}
}
public init(parsing value: String) {
let trimmedValue = value.trimmed
let optionalPrefix = "Optional<"
if trimmedValue.hasPrefix("@") {
let (attributes, resultString) = ["@autoclosure", "@escaping", "@noescape"]
.reduce(([], trimmedValue)) { acc, next -> ([String], String) in
var (attributes, resultString) = acc
guard let range = resultString.range(of: next) else { return acc }
resultString.removeSubrange(range)
attributes.append(next)
return (attributes, resultString)
}
self = .attributed(WrappableType(parsing: resultString), attributes: attributes)
} else if trimmedValue.hasSuffix("?") {
if trimmedValue.contains("->") && !trimmedValue.hasSuffix(")?") {
self = .type(trimmedValue)
} else {
self = .optional(WrappableType(parsing: String(trimmedValue.dropLast())))
}
} else if trimmedValue.hasPrefix(optionalPrefix) {
self = .optional(WrappableType(parsing: String(trimmedValue.dropFirst(optionalPrefix.count).dropLast())))
} else if trimmedValue.hasSuffix("!") {
self = .implicitlyUnwrappedOptional(WrappableType(parsing: String(trimmedValue.dropLast())))
} else {
self = .type(trimmedValue)
}
}
public func containsAttribute(named attribute: String) -> Bool {
switch self {
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
return wrapped.containsAttribute(named: attribute)
case .attributed(let wrapped, let attributes):
return attributes.contains(attribute.trimmed)
case .type(let typeString):
return false
}
}
}
extension WrappableType: CustomStringConvertible {
public var description: String {
return sugarized
}
}
extension WrappableType: Equatable {
public static func ==(lhs: WrappableType, rhs: WrappableType) -> Bool {
switch (lhs, rhs) {
case (.optional(let lhsWrapped), .optional(let rhsWrapped)),
(.implicitlyUnwrappedOptional(let lhsWrapped), .implicitlyUnwrappedOptional(let rhsWrapped)):
return lhsWrapped == rhsWrapped
case (.attributed(let lhsWrapped, let lhsAttributes), .attributed(let rhsWrapped, let rhsAttributes)):
return lhsWrapped == rhsWrapped && lhsAttributes == rhsAttributes
case (.type(let lhsType), .type(let rhsType)):
return lhsType == rhsType
default:
return false
}
}
}

View File

@ -32,6 +32,13 @@ extension String.UTF8View {
}
}
extension String {
func regexMatches(_ source: String) -> Bool {
let regex = try! NSRegularExpression(pattern: self)
return regex.firstMatch(in: source, range: NSRange(location: 0, length: source.count)) != nil
}
}
extension Sequence {
#if !swift(>=4.1)
public func compactMap<O>(_ transform: (Element) -> O?) -> [O] {
@ -51,8 +58,8 @@ extension Sequence {
internal func extractRange(from dictionary: [String: SourceKitRepresentable], offset: Key, length: Key) -> CountableRange<Int>? {
guard let
offset = (dictionary[offset.rawValue] as? Int64).map({ Int($0) }),
let length = (dictionary[length.rawValue] as? Int64).map({ Int($0) })
offset = (dictionary[offset.rawValue] as? Int64).map(Int.init),
let length = (dictionary[length.rawValue] as? Int64).map(Int.init)
else { return nil }
return offset..<offset.advanced(by: length)

View File

@ -31,8 +31,8 @@ List of all changes and new features can be found [here](CHANGELOG.md).
We are still missing support for some important features like:
* <del>inheritance (grandparent methods)</del>
* generics
* type inference for instance variables (you need to write it explicitly, otherwise it will be replaced with "__UnknownType")
* <del>generics</del>
* type inference for instance variables (you need to write it explicitly, otherwise it will be replaced with `__UnknownType`)
## What will not be supported
@ -180,6 +180,32 @@ stub(mock) { stub in
Notice the `get` and `set` these will be used in verification later.
##### Enabling default implementation
In addition to stubbing, you can enable default implementation using an instance of the original class that's being mocked. Every method/variable that is not stubbed will behave according to original implementation.
Enabling default implementation is achieved by simply calling the provided method:
```Swift
let original = OriginalClass<Int>(value: 12)
mock.enableDefaultImplementation(original)
```
For passing classes into the method, nothing changes whether you're mocking a class or a protocol. There however is a difference if you're using a `struct` to conform to the original protocol we are mocking:
```Swift
let original = ConformingStruct<String>(value: "Hello, Cuckoo!")
mock.enableDefaultImplementation(original)
// or if you need to track changes:
mock.enableDefaultImplementation(mutating: &original)
```
Note that this only concerns `struct`s. `enableDefaultImplementation(_:)` and `enableDefaultImplementation(mutating:)` are different in state tracking.
The standard non-mutating method `enableDefaultImplementation(_:)` creates a copy of the `struct` for default implementation and works with that. However, the mutating method `enableDefaultImplementation(mutating:)` takes a reference to the struct and the changes of the `original` are reflected in the default implementation calls even after enabling default implementation.
We recommend using the non-mutating method for enabling default implementation unless you need to track the changes for consistency within your code.
##### Chain stubbing
It is possible to chain stubbing. This is useful if you need to set different behavior for multiple calls in order. The last behavior will last for all other calls. Syntax goes like this:

View File

@ -17,6 +17,12 @@ public protocol Matchable {
var matcher: ParameterMatcher<MatchedType> { get }
}
public protocol OptionalMatchable {
associatedtype OptionalMatchedType
var optionalMatcher: ParameterMatcher<OptionalMatchedType?> { get }
}
public extension Matchable {
public func or<M>(_ otherMatchable: M) -> ParameterMatcher<MatchedType> where M: Matchable, M.MatchedType == MatchedType {
return ParameterMatcher {
@ -31,92 +37,125 @@ public extension Matchable {
}
}
extension Bool: Matchable {
public var matcher: ParameterMatcher<Bool> {
extension Optional: Matchable where Wrapped: Matchable, Wrapped.MatchedType == Wrapped {
public typealias MatchedType = Wrapped?
public var matcher: ParameterMatcher<Wrapped?> {
return ParameterMatcher<Wrapped?> { other in
switch (self, other) {
case (.none, .none):
return true
case (.some(let lhs), .some(let rhs)):
return lhs.matcher.matches(rhs)
default:
return false
}
}
}
}
extension Optional: OptionalMatchable where Wrapped: OptionalMatchable, Wrapped.OptionalMatchedType == Wrapped {
public typealias OptionalMatchedType = Wrapped
public var optionalMatcher: ParameterMatcher<Wrapped?> {
return ParameterMatcher<Wrapped?> { other in
switch (self, other) {
case (.none, .none):
return true
case (.some(let lhs), .some(let rhs)):
return lhs.optionalMatcher.matches(rhs)
default:
return false
}
}
}
}
extension Matchable where Self: Equatable {
public var matcher: ParameterMatcher<Self> {
return equal(to: self)
}
}
extension String: Matchable {
public var matcher: ParameterMatcher<String> {
return equal(to: self)
extension OptionalMatchable where OptionalMatchedType == Self, Self: Equatable {
public var optionalMatcher: ParameterMatcher<OptionalMatchedType?> {
return ParameterMatcher { other in
return Optional(self) == other
}
}
}
extension Float: Matchable {
public var matcher: ParameterMatcher<Float> {
return equal(to: self)
}
extension Bool: Matchable {}
extension Bool: OptionalMatchable {
public typealias OptionalMatchedType = Bool
}
extension Double: Matchable {
public var matcher: ParameterMatcher<Double> {
return equal(to: self)
}
extension String: Matchable {}
extension String: OptionalMatchable {
public typealias OptionalMatchedType = String
}
extension Character: Matchable {
public var matcher: ParameterMatcher<Character> {
return equal(to: self)
}
extension Float: Matchable {}
extension Float: OptionalMatchable {
public typealias OptionalMatchedType = Float
}
extension Int: Matchable {
public var matcher: ParameterMatcher<Int> {
return equal(to: self)
}
extension Double: Matchable {}
extension Double: OptionalMatchable {
public typealias OptionalMatchedType = Double
}
extension Int8: Matchable {
public var matcher: ParameterMatcher<Int8> {
return equal(to: self)
}
extension Character: Matchable {}
extension Character: OptionalMatchable {
public typealias OptionalMatchedType = Character
}
extension Int16: Matchable {
public var matcher: ParameterMatcher<Int16> {
return equal(to: self)
}
extension Int: Matchable {}
extension Int: OptionalMatchable {
public typealias OptionalMatchedType = Int
}
extension Int32: Matchable {
public var matcher: ParameterMatcher<Int32> {
return equal(to: self)
}
extension Int8: Matchable {}
extension Int8: OptionalMatchable {
public typealias OptionalMatchedType = Int8
}
extension Int64: Matchable {
public var matcher: ParameterMatcher<Int64> {
return equal(to: self)
}
extension Int16: Matchable {}
extension Int16: OptionalMatchable {
public typealias OptionalMatchedType = Int16
}
extension UInt: Matchable {
public var matcher: ParameterMatcher<UInt> {
return equal(to: self)
}
extension Int32: Matchable {}
extension Int32: OptionalMatchable {
public typealias OptionalMatchedType = Int32
}
extension UInt8: Matchable {
public var matcher: ParameterMatcher<UInt8> {
return equal(to: self)
}
extension Int64: Matchable {}
extension Int64: OptionalMatchable {
public typealias OptionalMatchedType = Int64
}
extension UInt16: Matchable {
public var matcher: ParameterMatcher<UInt16> {
return equal(to: self)
}
extension UInt: Matchable {}
extension UInt: OptionalMatchable {
public typealias OptionalMatchedType = UInt
}
extension UInt32: Matchable {
public var matcher: ParameterMatcher<UInt32> {
return equal(to: self)
}
extension UInt8: Matchable {}
extension UInt8: OptionalMatchable {
public typealias OptionalMatchedType = UInt8
}
extension UInt64: Matchable {
public var matcher: ParameterMatcher<UInt64> {
return equal(to: self)
}
extension UInt16: Matchable {}
extension UInt16: OptionalMatchable {
public typealias OptionalMatchedType = UInt16
}
extension UInt32: Matchable {}
extension UInt32: OptionalMatchable {
public typealias OptionalMatchedType = UInt32
}
extension UInt64: Matchable {}
extension UInt64: OptionalMatchable {
public typealias OptionalMatchedType = UInt64
}

View File

@ -22,3 +22,25 @@ public struct ParameterMatcher<T>: Matchable {
return matchesFunction(input)
}
}
public protocol CuckooOptionalType {
associatedtype Wrapped
static func from(optional: Optional<Wrapped>) -> Self
}
extension Optional: CuckooOptionalType {
public static func from(optional: Optional<Wrapped>) -> Optional<Wrapped> {
return optional
}
}
extension ParameterMatcher: OptionalMatchable where T: CuckooOptionalType {
public typealias OptionalMatchedType = T.Wrapped
public var optionalMatcher: ParameterMatcher<T.Wrapped?> {
return ParameterMatcher<T.Wrapped?> { other in
self.matchesFunction(T.from(optional: other))
}
}
}

View File

@ -45,11 +45,67 @@ public func anyString() -> ParameterMatcher<String> {
}
/// Returns a matcher matching any closure.
public func anyThrowingClosure<IN, OUT>() -> ParameterMatcher<(IN) throws -> OUT> {
public func anyThrowingClosure<OUT>() -> ParameterMatcher<() throws -> OUT> {
return ParameterMatcher()
}
public func anyClosure<IN, OUT>() -> ParameterMatcher<(IN) -> OUT> {
public func anyThrowingClosure<IN1, OUT>() -> ParameterMatcher<(IN1) throws -> OUT> {
return ParameterMatcher()
}
public func anyThrowingClosure<IN1, IN2, OUT>() -> ParameterMatcher<(IN1, IN2) throws -> OUT> {
return ParameterMatcher()
}
public func anyThrowingClosure<IN1, IN2, IN3, OUT>() -> ParameterMatcher<(IN1, IN2, IN3) throws -> OUT> {
return ParameterMatcher()
}
public func anyThrowingClosure<IN1, IN2, IN3, IN4, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4) throws -> OUT> {
return ParameterMatcher()
}
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5) throws -> OUT> {
return ParameterMatcher()
}
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, IN6, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6) throws -> OUT> {
return ParameterMatcher()
}
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, IN6, IN7, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6, IN7) throws -> OUT> {
return ParameterMatcher()
}
public func anyClosure<OUT>() -> ParameterMatcher<() -> OUT> {
return ParameterMatcher()
}
public func anyClosure<IN1, OUT>() -> ParameterMatcher<(IN1) -> OUT> {
return ParameterMatcher()
}
public func anyClosure<IN1, IN2, OUT>() -> ParameterMatcher<(IN1, IN2) -> OUT> {
return ParameterMatcher()
}
public func anyClosure<IN1, IN2, IN3, OUT>() -> ParameterMatcher<(IN1, IN2, IN3) -> OUT> {
return ParameterMatcher()
}
public func anyClosure<IN1, IN2, IN3, IN4, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4) -> OUT> {
return ParameterMatcher()
}
public func anyClosure<IN1, IN2, IN3, IN4, IN5, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5) -> OUT> {
return ParameterMatcher()
}
public func anyClosure<IN1, IN2, IN3, IN4, IN5, IN6, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6) -> OUT> {
return ParameterMatcher()
}
public func anyClosure<IN1, IN2, IN3, IN4, IN5, IN6, IN7, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6, IN7) -> OUT> {
return ParameterMatcher()
}

View File

@ -82,7 +82,7 @@ public class MockManager {
return stub
}
public func verify<IN, OUT>(_ method: String, callMatcher: CallMatcher, parameterMatchers: [ParameterMatcher<IN>], sourceLocation: SourceLocation) -> __DoNotUse<OUT> {
public func verify<IN, OUT>(_ method: String, callMatcher: CallMatcher, parameterMatchers: [ParameterMatcher<IN>], sourceLocation: SourceLocation) -> __DoNotUse<IN, OUT> {
var calls: [StubCall] = []
var indexesToRemove: [Int] = []
for (i, stubCall) in stubCalls.enumerated() {

View File

@ -6,7 +6,7 @@
// Copyright © 2016 Brightify. All rights reserved.
//
public protocol StubNoReturnThrowingFunction: StubFunctionThenTrait, StubFunctionThenDoNothingTrait, StubFunctionThenThrowTrait {
public protocol StubNoReturnThrowingFunction: StubFunctionThenTrait, StubFunctionThenDoNothingTrait, StubFunctionThenThrowTrait, StubFunctionThenThrowingTrait {
}
public struct ProtocolStubNoReturnThrowingFunction<IN>: StubNoReturnThrowingFunction {

View File

@ -6,7 +6,7 @@
// Copyright © 2016 Brightify. All rights reserved.
//
public protocol StubThrowingFunction: StubFunctionThenTrait, StubFunctionThenReturnTrait, StubFunctionThenThrowTrait {
public protocol StubThrowingFunction: StubFunctionThenTrait, StubFunctionThenReturnTrait, StubFunctionThenThrowTrait, StubFunctionThenThrowingTrait {
}
public struct ProtocolStubThrowingFunction<IN, OUT>: StubThrowingFunction {

View File

@ -0,0 +1,19 @@
//
// StubFunctionThenThrowingTrait.swift
// Cuckoo-iOS
//
// Created by Matyáš Kříž on 27/03/2019.
//
public protocol StubFunctionThenThrowingTrait: BaseStubFunctionTrait {
/// Invokes throwing `implementation` when invoked.
func then(_ implementation: @escaping (InputType) throws -> OutputType) -> Self
}
public extension StubFunctionThenThrowingTrait {
@discardableResult
func then(_ implementation: @escaping (InputType) throws -> OutputType) -> Self {
stub.appendAction(.callImplementation(implementation))
return self
}
}

View File

@ -20,16 +20,10 @@ public func wrap<M: Matchable, IN>(matchable: M, mapping: @escaping (IN) -> M.Ma
}
}
public func wrap<M: OptionalMatchable, IN, O>(matchable: M, mapping: @escaping (IN) -> M.OptionalMatchedType?) -> ParameterMatcher<IN> where M.OptionalMatchedType == O {
return ParameterMatcher {
return matchable.optionalMatcher.matches(mapping($0))
}
}
public typealias SourceLocation = (file: StaticString, line: UInt)
public func escapingStub<IN, OUT>(for closure: (IN) -> OUT) -> (IN) -> OUT {
return { _ in
fatalError("This is a stub! It's not supposed to be called!")
}
}
public func escapingStub<IN, OUT>(for closure: (IN) throws -> OUT) -> (IN) throws -> OUT {
return { _ in
fatalError("This is a stub! It's not supposed to be called!")
}
}

View File

@ -13,12 +13,12 @@ public struct VerifyProperty<T> {
private let sourceLocation: SourceLocation
@discardableResult
public func get() -> __DoNotUse<T> {
public func get() -> __DoNotUse<Void, T> {
return manager.verify(getterName(name), callMatcher: callMatcher, parameterMatchers: [] as [ParameterMatcher<Void>], sourceLocation: sourceLocation)
}
@discardableResult
public func set<M: Matchable>(_ matcher: M) -> __DoNotUse<Void> where M.MatchedType == T {
public func set<M: Matchable>(_ matcher: M) -> __DoNotUse<T, Void> where M.MatchedType == T {
return manager.verify(setterName(name), callMatcher: callMatcher, parameterMatchers: [matcher.matcher], sourceLocation: sourceLocation)
}

View File

@ -13,7 +13,7 @@ public struct VerifyReadOnlyProperty<T> {
private let sourceLocation: SourceLocation
@discardableResult
public func get() -> __DoNotUse<T> {
public func get() -> __DoNotUse<Void, T> {
return manager.verify(getterName(name), callMatcher: callMatcher, parameterMatchers: [] as [ParameterMatcher<Void>], sourceLocation: sourceLocation)
}

View File

@ -7,4 +7,4 @@
//
/// Marker struct for use as a return type in verification.
public struct __DoNotUse<T> { }
public struct __DoNotUse<IN, OUT> { }

View File

@ -14,15 +14,15 @@ extension TestedClass: Mocked {
}
class ClassTest: XCTestCase {
private var mock: MockTestedClass!
override func setUp() {
super.setUp()
mock = MockTestedClass()
}
func testReadOnlyPropertyWithMockCreator() {
let mock = createMock(for: TestedClass.self) { builder, stub in
when(stub.readOnlyProperty.get).thenReturn("a")
@ -44,119 +44,119 @@ class ClassTest: XCTestCase {
XCTAssertEqual(mock.readOnlyProperty, "a")
_ = verify(mock).readOnlyProperty.get
}
func testReadWriteProperty() {
var called = false
stub(mock) { mock in
when(mock.readWriteProperty.get).thenReturn(1)
when(mock.readWriteProperty.set(anyInt())).then { _ in called = true }
}
mock.readWriteProperty = 0
XCTAssertEqual(mock.readWriteProperty, 1)
XCTAssertTrue(called)
_ = verify(mock).readWriteProperty.get
verify(mock).readWriteProperty.set(0)
}
func testOptionalProperty() {
var called = false
stub(mock) { mock in
when(mock.optionalProperty.get).thenReturn(nil)
when(mock.optionalProperty.set(anyInt())).then { _ in called = true }
}
mock.optionalProperty = 0
XCTAssertNil(mock.optionalProperty)
XCTAssertTrue(called)
_ = verify(mock).optionalProperty.get
verify(mock).optionalProperty.set(equal(to: 0))
}
func testNoReturn() {
var called = false
stub(mock) { mock in
when(mock.noReturn()).then { _ in called = true }
}
mock.noReturn()
XCTAssertTrue(called)
verify(mock).noReturn()
}
func testCountCharacters() {
stub(mock) { mock in
when(mock.count(characters: "a")).thenReturn(1)
}
XCTAssertEqual(mock.count(characters: "a"), 1)
verify(mock).count(characters: "a")
}
func testWithThrows() {
stub(mock) { mock in
when(mock.withThrows()).thenThrow(TestError.unknown)
}
var catched = false
do {
_ = try mock.withThrows()
} catch {
catched = true
}
XCTAssertTrue(catched)
verify(mock).withThrows()
}
func testWithNoReturnThrows() {
stub(mock) { mock in
when(mock.withNoReturnThrows()).thenThrow(TestError.unknown)
}
var catched = false
do {
try mock.withNoReturnThrows()
} catch {
catched = true
}
XCTAssertTrue(catched)
verify(mock).withNoReturnThrows()
}
func testWithClosure() {
stub(mock) { mock in
when(mock.withClosure(anyClosure())).then { $0("a") }
}
XCTAssertEqual(mock.withClosure { _ in 1 }, 1)
verify(mock).withClosure(anyClosure())
}
func testWithEscape() {
var called = false
stub(mock) { mock in
when(mock.withEscape(anyString(), action: anyClosure())).then { text, closure in closure(text) }
}
mock.withEscape("a") { called = $0 == "a" }
XCTAssertTrue(called)
verify(mock).withEscape(anyString(), action: anyClosure())
}
func testWithOptionalClosure() {
var called = false
stub(mock) { mock in
when(mock.withOptionalClosure(anyString(), closure: anyClosure())).then { text, closure in closure?(text) }
}
mock.withOptionalClosure("a") { called = $0 == "a" }
XCTAssertTrue(called)
verify(mock).withOptionalClosure(anyString(), closure: anyClosure())
}
@ -189,8 +189,256 @@ class ClassTest: XCTestCase {
XCTAssertEqual(mock.callingCountCharactersMethodWithHello(), 0)
verify(mock).callingCountCharactersMethodWithHello()
}
func testInout() {
let mock = MockInoutMethodClass()
stub(mock) { mock in
when(mock.inoutko(param: anyInt())).then { param in
print(param)
}
}
var integer = 12
mock.inoutko(param: &integer)
}
func testOptionals() {
let mock = MockOptionalParamsClass()
stub(mock) { mock in
when(mock.clashingFunction(param1: Optional(1), param2: "Henlo Fren") as Cuckoo.ClassStubNoReturnFunction<(Int?, String)>).thenDoNothing()
when(mock.clashingFunction(param1: Optional.none, param2: "Henlo Fren") as Cuckoo.ClassStubNoReturnFunction<(Int?, String)>).thenDoNothing()
when(mock.clashingFunction(param1: anyInt(), param2: "What's the question?") as Cuckoo.ClassStubNoReturnFunction<(Int, String?)>).then {
print("What's 6 times 9? \($0.0)")
}
when(mock.clashingFunction(param1: anyInt(), param2: isNil()) as Cuckoo.ClassStubNoReturnFunction<(Int, String?)>).then {
print("What's 6 times 9? \($0.0)")
}
when(mock.function(param: "string")).thenDoNothing()
}
mock.clashingFunction(param1: Optional(1), param2: "Henlo Fren")
mock.clashingFunction(param1: nil, param2: "Henlo Fren")
mock.clashingFunction(param1: 42, param2: Optional("What's the question?"))
_ = verify(mock).clashingFunction(param1: anyInt(), param2: "Henlo Fren") as Cuckoo.__DoNotUse<(Int?, String), Void>
_ = verify(mock).clashingFunction(param1: isNil(), param2: "Henlo Fren") as Cuckoo.__DoNotUse<(Int?, String), Void>
_ = verify(mock).clashingFunction(param1: 42, param2: "What's the question?") as Cuckoo.__DoNotUse<(Int, String?), Void>
}
func testClosureN() {
let mock = MockClosureNClass()
stub(mock) { mock in
when(mock.f0(closure: anyClosure())).then { closure in
closure()
}
when(mock.f1(closure: anyClosure())).then { closure in
_ = closure("Hello")
}
when(mock.f2(closure: anyClosure())).then { closure in
_ = closure("Cuckoo", 7)
}
when(mock.f3(closure: anyClosure())).then { closure in
_ = closure("World", 1, true)
}
when(mock.f4(closure: anyClosure())).then { closure in
_ = closure("Dude", 0, false, Optional(["Hello", "World"])) ?? ["defaultko"]
}
when(mock.f5(closure: anyClosure())).then { closure in
_ = closure("How", 2, true, nil, Set([1, 2, 3]))
}
when(mock.f6(closure: anyClosure())).then { closure in
_ = closure("Are", 5, true, nil, Set([1, 2, 3]), ())
}
when(mock.f7(closure: anyClosure())).then { closure in
_ = closure("You", 13, false, nil, Set([1, 2]), (), ["hello": "world"])
}
}
mock.f0(closure: { })
mock.f1(closure: { $0 })
mock.f2(closure: { $1 })
mock.f3(closure: { $2 })
mock.f4(closure: { $3 })
mock.f5(closure: { $4 })
mock.f6(closure: { $5 })
mock.f7(closure: { $6 })
verify(mock).f0(closure: anyClosure())
verify(mock).f1(closure: anyClosure())
verify(mock).f2(closure: anyClosure())
verify(mock).f3(closure: anyClosure())
verify(mock).f4(closure: anyClosure())
verify(mock).f5(closure: anyClosure())
verify(mock).f6(closure: anyClosure())
verify(mock).f7(closure: anyClosure())
}
func testClosureNThrowing() {
let mock = MockClosureNThrowingClass()
stub(mock) { mock in
when(mock.f0(closure: anyThrowingClosure())).then { closure in
_ = try? closure()
}
when(mock.f1(closure: anyThrowingClosure())).then { closure in
_ = try? closure("Hello")
}
when(mock.f2(closure: anyThrowingClosure())).then { closure in
_ = try? closure("Cuckoo", 7)
}
when(mock.f3(closure: anyThrowingClosure())).then { closure in
_ = try? closure("World", 1, true)
}
when(mock.f4(closure: anyThrowingClosure())).then { closure in
_ = try? closure("Dude", 0, false, Optional(["Hello", "World"]))
}
when(mock.f5(closure: anyThrowingClosure())).then { closure in
_ = try? closure("How", 2, true, nil, Set([1, 2, 3]))
}
when(mock.f6(closure: anyThrowingClosure())).then { closure in
_ = try? closure("Are", 5, true, nil, Set([1, 2, 3]), ())
}
when(mock.f7(closure: anyThrowingClosure())).then { closure in
_ = try? closure("You", 13, false, nil, Set([1, 2]), (), ["hello": "world"])
}
}
mock.f0(closure: { })
mock.f1(closure: { $0 })
mock.f2(closure: { $1 })
mock.f3(closure: { $2 })
mock.f4(closure: { $3 })
mock.f5(closure: { $4 })
mock.f6(closure: { $5 })
mock.f7(closure: { $6 })
verify(mock).f0(closure: anyThrowingClosure())
verify(mock).f1(closure: anyThrowingClosure())
verify(mock).f2(closure: anyThrowingClosure())
verify(mock).f3(closure: anyThrowingClosure())
verify(mock).f4(closure: anyThrowingClosure())
verify(mock).f5(closure: anyThrowingClosure())
verify(mock).f6(closure: anyThrowingClosure())
verify(mock).f7(closure: anyThrowingClosure())
}
func testClosureNThrowingThrows() {
let mock = MockClosureNThrowingThrowsClass()
stub(mock) { mock in
when(mock.f0(closure: anyThrowingClosure())).then { closure in
try closure()
}
when(mock.f1(closure: anyThrowingClosure())).then { closure in
_ = try closure("Hello")
}
when(mock.f2(closure: anyThrowingClosure())).then { closure in
_ = try closure("Cuckoo", 7)
}
when(mock.f3(closure: anyThrowingClosure())).then { closure in
_ = try closure("World", 1, true)
}
when(mock.f4(closure: anyThrowingClosure())).then { closure in
_ = try closure("Dude", 0, false, Optional(["Hello", "World"])) ?? ["defaultko"]
}
when(mock.f5(closure: anyThrowingClosure())).then { closure in
_ = try closure("How", 2, true, nil, Set([1, 2, 3]))
}
when(mock.f6(closure: anyThrowingClosure())).then { closure in
_ = try closure("Are", 5, true, nil, Set([1, 2, 3]), ())
}
when(mock.f7(closure: anyThrowingClosure())).then { closure in
_ = try closure("You", 13, false, nil, Set([1, 2]), (), ["hello": "world"])
}
}
struct Erre: Error {
}
XCTAssertThrowsError(try mock.f0(closure: { throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f1(closure: { _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f2(closure: { _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f3(closure: { _, _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f4(closure: { _, _, _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f5(closure: { _, _, _, _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f6(closure: { _, _, _, _, _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f7(closure: { _, _, _, _, _, _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
verify(mock).f0(closure: anyThrowingClosure())
verify(mock).f1(closure: anyThrowingClosure())
verify(mock).f2(closure: anyThrowingClosure())
verify(mock).f3(closure: anyThrowingClosure())
verify(mock).f4(closure: anyThrowingClosure())
verify(mock).f5(closure: anyThrowingClosure())
verify(mock).f6(closure: anyThrowingClosure())
verify(mock).f7(closure: anyThrowingClosure())
}
func testClosureNRehrowing() {
let mock = MockClosureNRethrowingClass()
stub(mock) { mock in
when(mock.f0(closure: anyThrowingClosure())).then { closure in
_ = try closure()
}
when(mock.f1(closure: anyThrowingClosure())).then { closure in
_ = try? closure("Hello")
}
when(mock.f2(closure: anyThrowingClosure())).then { closure in
_ = try closure("Cuckoo", 7)
}
when(mock.f3(closure: anyThrowingClosure())).then { closure in
_ = try? closure("World", 1, true)
}
when(mock.f4(closure: anyThrowingClosure())).then { closure in
_ = try closure("Dude", 0, false, Optional(["Hello", "World"])) ?? ["defaultko"]
}
when(mock.f5(closure: anyThrowingClosure())).then { closure in
_ = try? closure("How", 2, true, nil, Set([1, 2, 3]))
}
when(mock.f6(closure: anyThrowingClosure())).then { closure in
_ = try closure("Are", 5, true, nil, Set([1, 2, 3]), ())
}
when(mock.f7(closure: anyThrowingClosure())).then { closure in
_ = try? closure("You", 13, false, nil, Set([1, 2]), (), ["hello": "world"])
}
}
// testing that when the closure is not throwing, no need to call it with `try`
mock.f0(closure: { })
mock.f1(closure: { $0 })
mock.f2(closure: { $1 })
mock.f3(closure: { $2 })
mock.f4(closure: { $3 })
mock.f5(closure: { $4 })
mock.f6(closure: { $5 })
mock.f7(closure: { $6 })
// testing the cases where stub calls closure with `try`
XCTAssertThrowsError(try mock.f0(closure: { throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f2(closure: { _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f4(closure: { _, _, _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
XCTAssertThrowsError(try mock.f6(closure: { _, _, _, _, _, _ in throw TestError.unknown }), "Expected thrown TestError.", TestError.errorCheck())
verify(mock, times(2)).f0(closure: anyThrowingClosure())
verify(mock).f1(closure: anyThrowingClosure())
verify(mock, times(2)).f2(closure: anyThrowingClosure())
verify(mock).f3(closure: anyThrowingClosure())
verify(mock, times(2)).f4(closure: anyThrowingClosure())
verify(mock).f5(closure: anyThrowingClosure())
verify(mock, times(2)).f6(closure: anyThrowingClosure())
verify(mock).f7(closure: anyThrowingClosure())
}
private enum TestError: Error {
case unknown
static func errorCheck(file: StaticString = #file, line: UInt = #line) -> (Error) -> Void {
return {
if $0 is TestError {
} else {
XCTFail("Expected TestError, got: \(type(of: $0))(\($0.localizedDescription))", file: file, line: line)
}
}
}
}
}

View File

@ -0,0 +1,178 @@
//
// GenericClassTest.swift
// Cuckoo
//
// Created by Matyáš Kříž on 26/11/2018.
//
import XCTest
import Cuckoo
extension GenericClass: Mocked {
typealias MockType = MockGenericClass<T, U, V>
}
class GenericClassTest: XCTestCase {
private var mock: MockGenericClass<Int, String, Bool>!
private var original: GenericClass<Int, String, Bool>!
override func setUp() {
super.setUp()
mock = MockGenericClass(theT: 10, theU: "Hello, generics!", theV: false)
original = GenericClass(theT: 42, theU: "Hello world!", theV: false)
}
func testReadWritePropertyWithMockCreator() {
let mock = createMock(for: GenericClass<Int, String, Bool>.self) { builder, stub in
when(stub.readWritePropertyU.get).thenReturn("a")
return MockGenericClass(theT: 0, theU: "", theV: true)
}
XCTAssertEqual(mock.readWritePropertyU, "a")
_ = verify(mock).readWritePropertyU.get
}
func testReadWriteProperty() {
stub(mock) { mock in
when(mock.readWritePropertyV.get).thenReturn(true)
}
XCTAssertEqual(mock.readWritePropertyV, true)
_ = verify(mock).readWritePropertyV.get
}
func testConstantProperty() {
mock.enableDefaultImplementation(original)
XCTAssertEqual(mock.constant, 10.0)
}
func testModification() {
mock.enableDefaultImplementation(original)
let numbers = [127, 0, 0, 1]
for number in numbers {
original.readWritePropertyT = number
XCTAssertEqual(mock.readWritePropertyT, number)
}
verify(mock, times(numbers.count)).readWritePropertyT.get
}
func testReadWriteProperties() {
var calledT = false
var calledU = false
var calledV = false
stub(mock) { mock in
when(mock.readWritePropertyT.get).thenReturn(1)
when(mock.readWritePropertyT.set(anyInt())).then { _ in calledT = true }
when(mock.readWritePropertyU.get).thenReturn("Hello, mocker!")
when(mock.readWritePropertyU.set(anyString())).then { _ in calledU = true }
when(mock.readWritePropertyV.get).thenReturn(false)
when(mock.readWritePropertyV.set(any(Bool.self))).then { _ in calledV = true }
}
mock.readWritePropertyT = 0
XCTAssertEqual(mock.readWritePropertyT, 1)
XCTAssertTrue(calledT)
_ = verify(mock).readWritePropertyT.get
verify(mock).readWritePropertyT.set(0)
mock.readWritePropertyU = "NO MOCKING FOR YOU"
XCTAssertEqual(mock.readWritePropertyU, "Hello, mocker!")
XCTAssertTrue(calledU)
_ = verify(mock).readWritePropertyU.get
verify(mock).readWritePropertyU.set("NO MOCKING FOR YOU")
mock.readWritePropertyV = true
XCTAssertEqual(mock.readWritePropertyV, false)
XCTAssertTrue(calledV)
_ = verify(mock).readWritePropertyV.get
verify(mock).readWritePropertyV.set(true)
}
func testOptionalProperty() {
var called = false
stub(mock) { mock in
when(mock.optionalProperty.get).thenReturn(nil)
when(mock.optionalProperty.set(anyString())).then { _ in called = true }
}
mock.optionalProperty = "tukabel"
XCTAssertNil(mock.optionalProperty)
XCTAssertTrue(called)
_ = verify(mock).optionalProperty.get
verify(mock).optionalProperty.set(equal(to: "tukabel"))
}
func testNoReturn() {
var called = false
stub(mock) { mock in
when(mock.noReturn()).then { _ in called = true }
}
mock.noReturn()
XCTAssertTrue(called)
verify(mock).noReturn()
}
func testUnequal() {
stub(mock) { mock in
when(mock.unequal(one: false, two: false)).thenReturn(false)
when(mock.unequal(one: false, two: true)).thenReturn(true)
when(mock.unequal(one: true, two: false)).thenReturn(true)
when(mock.unequal(one: true, two: true)).thenReturn(false)
}
XCTAssertFalse(mock.unequal(one: false, two: false))
XCTAssertTrue(mock.unequal(one: false, two: true))
XCTAssertTrue(mock.unequal(one: true, two: false))
XCTAssertFalse(mock.unequal(one: true, two: true))
}
func testGetThird() {
stub(mock) { mock in
when(mock.getThird(foo: anyInt(), bar: "gimme true", baz: any(Bool.self))).thenReturn(false)
when(mock.getThird(foo: anyInt(), bar: "gimme false", baz: any(Bool.self))).thenReturn(true)
}
XCTAssertFalse(mock.getThird(foo: 10, bar: "gimme true", baz: true))
verify(mock).getThird(foo: 10, bar: "gimme true", baz: true)
XCTAssertTrue(mock.getThird(foo: 1099, bar: "gimme false", baz: false))
verify(mock).getThird(foo: 1099, bar: "gimme false", baz: false)
}
func testPrint() {
stub(mock) { mock in
when(mock.print(theT: anyInt())).thenDoNothing()
}
mock.print(theT: 555)
verify(mock).print(theT: 555)
}
func testEncode() {
mock.enableDefaultImplementation(original)
stub(mock) { mock in
when(mock.encode(theU: anyString())).thenCallRealImplementation()
}
let encoder = JSONEncoder()
let world = "Hello, world!"
XCTAssertEqual(mock.encode(theU: world), try! encoder.encode(["root": world]))
verify(mock).encode(theU: world)
}
func testWithClosure() {
stub(mock) { mock in
when(mock.withClosure(anyClosure())).then { $0(666) }
}
XCTAssertEqual(mock.withClosure { number in number * 2 + 5 }, 1337)
verify(mock).withClosure(anyClosure())
}
}

View File

@ -0,0 +1,192 @@
//
// GenericProtocolTest.swift
// Cuckoo
//
// Created by Matyáš Kříž on 26/11/2018.
//
import XCTest
import Cuckoo
private class GenericProtocolConformerClass<C: AnyObject, V>: GenericProtocol {
let readOnlyPropertyC: C
var readWritePropertyV: V
let constant: Int = 0
var optionalProperty: V?
required init(theC: C, theV: V) {
readOnlyPropertyC = theC
readWritePropertyV = theV
}
func callSomeC(theC: C) -> Int {
return 1
}
func callSomeV(theV: V) -> Int {
switch theV {
case let int as Int:
return int
case let string as String:
return Int(string) ?? 8008135
default:
return 0
}
}
func compute(classy: C, value: V) -> C {
guard let testyClassy = classy as? TestedClass else { return classy }
switch value {
case let int as Int:
testyClassy.readWriteProperty = int
case _ as String:
testyClassy.optionalProperty = nil
default:
break
}
return testyClassy as! C
}
func noReturn() {}
}
private struct GenericProtocolConformerStruct<C: AnyObject, V>: GenericProtocol {
let readOnlyPropertyC: C
var readWritePropertyV: V
let constant: Int = 0
var optionalProperty: V?
init(theC: C, theV: V) {
readOnlyPropertyC = theC
readWritePropertyV = theV
}
func callSomeC(theC: C) -> Int {
return 1
}
func callSomeV(theV: V) -> Int {
return 0
}
func compute(classy: C, value: V) -> C {
return classy
}
func noReturn() {}
}
class GenericProtocolTest: XCTestCase {
private func createMock<V>(value: V) -> MockGenericProtocol<MockTestedClass, V> {
let classy = MockTestedClass()
return MockGenericProtocol(theC: classy, theV: value)
}
func testReadOnlyProperty() {
let mock = createMock(value: 10)
stub(mock) { mock in
when(mock.readOnlyPropertyC.get).thenReturn(MockTestedClass())
}
_ = verify(mock).readOnlyPropertyC.get
}
func testReadWriteProperty() {
let mock = createMock(value: 10)
stub(mock) { mock in
when(mock.readWritePropertyV.get).then { 11 }
when(mock.readWritePropertyV.set(anyInt())).thenDoNothing()
}
mock.readWritePropertyV = 42
XCTAssertEqual(mock.readWritePropertyV, 11)
_ = verify(mock).readWritePropertyV.get
verify(mock).readWritePropertyV.set(42)
}
func testOptionalProperty() {
let mock = createMock(value: false)
var called = false
stub(mock) { mock in
when(mock.optionalProperty.get).thenReturn(true)
when(mock.optionalProperty.set(any(Bool?.self))).then { _ in called = true }
}
mock.optionalProperty = false
XCTAssertTrue(mock.optionalProperty == true)
XCTAssertTrue(called)
_ = verify(mock).optionalProperty.get
verify(mock).optionalProperty.set(equal(to: false))
}
func testNoReturn() {
let mock = createMock(value: "Hello. Sniffing through tests? If you're having trouble with Cuckoo, shoot us a message!")
var called = false
stub(mock) { mock in
when(mock.noReturn()).then { _ in called = true }
}
mock.noReturn()
XCTAssertTrue(called)
verify(mock).noReturn()
}
func testModification() {
let mock = createMock(value: ["EXTERMINATE!": "EXTERMINATE!!", "EXTERMINATE!!!": "EXTERMINATE!!!!"])
let original = GenericProtocolConformerClass(theC: MockTestedClass(), theV: ["Sir, may I help you?": "Nope, just lookin' 👀"])
mock.enableDefaultImplementation(original)
original.readWritePropertyV["Are you sure?"] = "Yeah, I'm just waiting for my wife."
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀", "Are you sure?": "Yeah, I'm just waiting for my wife."])
original.readWritePropertyV["Alright, have a nice weekend!"] = "Thanks, you too."
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀",
"Are you sure?": "Yeah, I'm just waiting for my wife.",
"Alright, have a nice weekend!": "Thanks, you too."])
verify(mock, times(2)).readWritePropertyV.get
}
// the next two test cases show using a struct as the default implementation and changing its state:
// - NOTE: This only applies for `struct`s, not `class`es.
// using: `enableDefaultImplementation(mutating:)` reflects the original's state at all times
func testStructModification() {
let mock = createMock(value: ["EXTERMINATE!": "EXTERMINATE!!", "EXTERMINATE!!!": "EXTERMINATE!!!!"])
var original = GenericProtocolConformerStruct(theC: MockTestedClass(), theV: ["Sir, may I help you?": "Nope, just lookin' 👀"])
mock.enableDefaultImplementation(mutating: &original)
original.readWritePropertyV["Are you sure?"] = "Yeah, I'm just waiting for my wife."
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀", "Are you sure?": "Yeah, I'm just waiting for my wife."])
original.readWritePropertyV["Alright, have a nice weekend!"] = "Thanks, you too."
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀",
"Are you sure?": "Yeah, I'm just waiting for my wife.",
"Alright, have a nice weekend!": "Thanks, you too."])
verify(mock, times(2)).readWritePropertyV.get
}
// using: `enableDefaultImplementation(_:)` reflects the original's state at the time of enabling default implementation with the struct
//
func testStructNonModification() {
let mock = createMock(value: ["EXTERMINATE!": "EXTERMINATE!!", "EXTERMINATE!!!": "EXTERMINATE!!!!"])
var original = GenericProtocolConformerStruct(theC: MockTestedClass(), theV: ["Sir, may I help you?": "Nope, just lookin' 👀"])
mock.enableDefaultImplementation(original)
original.readWritePropertyV["Are you sure?"] = "Yeah, I'm just waiting for my wife."
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀"])
XCTAssertEqual(original.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀", "Are you sure?": "Yeah, I'm just waiting for my wife."])
original.readWritePropertyV["Alright, have a nice weekend!"] = "Thanks, you too."
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀"])
XCTAssertEqual(original.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀",
"Are you sure?": "Yeah, I'm just waiting for my wife.",
"Alright, have a nice weekend!": "Thanks, you too."])
verify(mock, times(2)).readWritePropertyV.get
}
}

View File

@ -63,12 +63,36 @@ class ParameterMatcherFunctionsTest: XCTestCase {
}
func testAnyClosure() {
XCTAssertTrue(anyClosure().matches({ 0 }))
XCTAssertTrue((anyClosure() as ParameterMatcher<() -> Int>).matches { 0 })
XCTAssertTrue((anyClosure() as ParameterMatcher<(Int) -> Int>).matches { _ in 0 })
XCTAssertTrue((anyClosure() as ParameterMatcher<(Int, Int) -> Int>).matches { _, _ in 0 })
XCTAssertTrue((anyClosure() as ParameterMatcher<(Int, Int, Int) -> Int>).matches { _, _, _ in 0 })
XCTAssertTrue((anyClosure() as ParameterMatcher<(Int, Int, Int, Int) -> Int>).matches { _, _, _, _ in 0 })
XCTAssertTrue((anyClosure() as ParameterMatcher<(Int, Int, Int, Int, Int) -> Int>).matches { _, _, _, _, _ in 0 })
XCTAssertTrue((anyClosure() as ParameterMatcher<(Int, Int, Int, Int, Int, Int) -> Int>).matches { _, _, _, _, _, _ in 0 })
XCTAssertTrue((anyClosure() as ParameterMatcher<(Int, Int, Int, Int, Int, Int, Int) -> Int>).matches { _, _, _, _, _, _, _ in 0 })
}
func testAnyThrowingClosure() {
XCTAssertTrue(anyThrowingClosure().matches { 0 })
XCTAssertTrue(anyThrowingClosure().matches { (p: Int) throws in 1 })
struct MockError: Error { }
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<() throws -> Int>).matches { 0 })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int) throws -> Int>).matches { _ in 0 })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int) throws -> Int>).matches { _, _ in 0 })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int) throws -> Int>).matches { _, _, _ in 0 })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _ in 0 })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _ in 0 })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _, _ in 0 })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _, _, _ in 0 })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<() throws -> Int>).matches { throw MockError() })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int) throws -> Int>).matches { _ in throw MockError() })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int) throws -> Int>).matches { _, _ in throw MockError() })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int) throws -> Int>).matches { _, _, _ in throw MockError() })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _ in throw MockError() })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _ in throw MockError() })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _, _ in throw MockError() })
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _, _, _ in throw MockError() })
}
func testAnyOptionalThrowingClosure() {

View File

@ -0,0 +1,12 @@
//
// CollisionClasses.swift
// Cuckoo
//
// Created by Matyáš Kříž on 31/01/2019.
//
import Foundation
// should generate a compiler error if `FinalClass` isn't ignored and `MockFinalClass` is generated
class MockFinalClass {}
class FinalClassStub {}

View File

@ -0,0 +1,47 @@
//
// GenericClass.swift
// Cuckoo-iOS
//
// Created by Matyáš Kříž on 19/11/2018.
//
import Foundation
class GenericClass<T: CustomStringConvertible, U: Codable & CustomStringConvertible, V: Hashable & Equatable> {
let constant = 10.0
var readWritePropertyT: T
var readWritePropertyU: U
var readWritePropertyV: V
var optionalProperty: U?
init(theT: T, theU: U, theV: V) {
readWritePropertyT = theT
readWritePropertyU = theU
readWritePropertyV = theV
}
func unequal(one: V, two: V) -> Bool {
return one != two
}
func getThird(foo: T, bar: U, baz: V) -> V {
return baz
}
func print(theT: T) {
Swift.print(theT)
}
func encode(theU: U) -> Data {
let encoder = JSONEncoder()
return try! encoder.encode(["root": theU])
}
func withClosure(_ closure: (T) -> Int) -> Int {
return closure(readWritePropertyT)
}
func noReturn() {}
}

View File

@ -0,0 +1,26 @@
//
// GenericProtocol.swift
// Cuckoo
//
// Created by Matyáš Kříž on 19/11/2018.
//
import Foundation
protocol GenericProtocol {
associatedtype C: AnyObject
associatedtype V
var readOnlyPropertyC: C { get }
var readWritePropertyV: V { get set }
var constant: Int { get }
var optionalProperty: V? { get set }
init(theC: C, theV: V)
func callSomeC(theC: C) -> Int
func callSomeV(theV: V) -> Int
func compute(classy: C, value: V) -> C
func noReturn()
}

View File

@ -12,7 +12,7 @@ import UIKit
@available(swift 4.0)
class TestedClass {
let constant: Float = 0.0
private(set) var privateSetProperty: Int = 0
@ -20,33 +20,33 @@ class TestedClass {
var readOnlyProperty: String {
return "a"
}
lazy var readWriteProperty: Int = 0
lazy var optionalProperty: Int? = 0
func noReturn() {
}
func count(characters: String) -> Int {
return characters.count
}
func withThrows() throws -> Int {
return 0
}
func withNoReturnThrows() throws {
}
func withClosure(_ closure: (String) -> Int) -> Int {
return closure("hello")
}
func withEscape(_ a: String, action closure: @escaping (String) -> Void) {
closure(a)
}
func withOptionalClosure(_ a: String, closure: ((String) -> Void)?) {
closure?(a)
}
@ -201,6 +201,120 @@ final class FinalClass {
var shouldBeIgnoredByCuckoo = true
}
// should generate a compiler error if `FinalClass` isn't ignored and `MockFinalClass` is generated
final class MockFinalClass {}
final class FinalClassStub {}
protocol GenericFunctionProtocol {
func method<T>(param: T) where T: CustomStringConvertible, T: StringProtocol
}
class GenericFunctionClass {
func method<T>(param: T) where T: CustomStringConvertible {
}
}
class OptionalParamsClass {
func function(param: String?) { }
// the next two methods are exactly the same except for parameter types
// this is not ambiguous for Swift, however, mocking this in a way so
// that the stubbing and verifying calls wouldn't be ambiguous would require
// some serious amount of hacks, so we decided to postpone this feature for now
// see `clashingFunction` calls in tests to see how to disambiguate if you're ever
// in need of two almost identical methods
func clashingFunction(param1: Int?, param2: String) { }
func clashingFunction(param1: Int, param2: String?) { }
}
public class InternalFieldsInPublicClass {
internal var field: Int? = nil
private(set) var privateSetField: Int? = nil
internal func function() { }
}
class FinalFields {
final var field: Int? = nil
final func function() { }
}
class InoutMethodClass {
func inoutko(param: inout Int) { }
func inoutkoMultiple(param1: inout Int, param2: inout String, param3: Void) { }
func inoutkoClosure(param: (inout Int) -> Void) { }
}
class ClosureNClass {
func f0(closure: () -> Void) { }
func f1(closure: (String) -> String) { }
func f2(closure: (String, Int) -> Int) { }
func f3(closure: (String, Int, Bool) -> Bool) { }
func f4(closure: (String, Int, Bool, [String]?) -> [String]?) { }
func f5(closure: (String, Int, Bool, [String]?, Set<Int>) -> Set<Int>) { }
func f6(closure: (String, Int, Bool, [String]?, Set<Int>, Void) -> Void) { }
func f7(closure: (String, Int, Bool, [String]?, Set<Int>, Void, [String: String]) -> [String: String]) { }
}
class ClosureNThrowingClass {
func f0(closure: () throws -> Void) { }
func f1(closure: (String) throws -> String) { }
func f2(closure: (String, Int) throws -> Int) { }
func f3(closure: (String, Int, Bool) throws -> Bool) { }
func f4(closure: (String, Int, Bool, [String]?) throws -> [String]?) { }
func f5(closure: (String, Int, Bool, [String]?, Set<Int>) throws -> Set<Int>) { }
func f6(closure: (String, Int, Bool, [String]?, Set<Int>, Void) throws -> Void) { }
func f7(closure: (String, Int, Bool, [String]?, Set<Int>, Void, [String: String]) throws -> [String: String]) { }
}
class ClosureNThrowingThrowsClass {
func f0(closure: () throws -> Void) throws { }
func f1(closure: (String) throws -> String) throws { }
func f2(closure: (String, Int) throws -> Int) throws { }
func f3(closure: (String, Int, Bool) throws -> Bool) throws { }
func f4(closure: (String, Int, Bool, [String]?) throws -> [String]?) throws { }
func f5(closure: (String, Int, Bool, [String]?, Set<Int>) throws -> Set<Int>) throws { }
func f6(closure: (String, Int, Bool, [String]?, Set<Int>, Void) throws -> Void) throws { }
func f7(closure: (String, Int, Bool, [String]?, Set<Int>, Void, [String: String]) throws -> [String: String]) throws { }
}
class ClosureNRethrowingClass {
func f0(closure: () throws -> Void) rethrows { }
func f1(closure: (String) throws -> String) rethrows { }
func f2(closure: (String, Int) throws -> Int) rethrows { }
func f3(closure: (String, Int, Bool) throws -> Bool) rethrows { }
func f4(closure: (String, Int, Bool, [String]?) throws -> [String]?) rethrows { }
func f5(closure: (String, Int, Bool, [String]?, Set<Int>) throws -> Set<Int>) rethrows { }
func f6(closure: (String, Int, Bool, [String]?, Set<Int>, Void) throws -> Void) rethrows { }
func f7(closure: (String, Int, Bool, [String]?, Set<Int>, Void, [String: String]) throws -> [String: String]) rethrows { }
}

View File

@ -150,6 +150,7 @@ class StubbingTest: XCTestCase {
when(stub.withNamedTuple(tuple: any())).thenReturn(11)
when(stub.subclassMethod()).thenReturn(12)
when(stub.withOptionalClosureAndReturn(anyString(), closure: isNil())).thenReturn(2)
when(stub.withOptionalClosureAndReturn(anyString(), closure: anyClosure())).thenReturn(3)
when(stub.withClosureAndParam(anyString(), closure: anyClosure())).thenReturn(3)
when(stub.withMultClosures(closure: anyClosure(), closureB: anyClosure(), closureC: anyClosure())).thenReturn(4)
when(stub.withThrowingClosure(closure: anyThrowingClosure())).thenReturn("throwing closure")
@ -202,7 +203,8 @@ class StubbingTest: XCTestCase {
XCTAssertEqual(try! mock.withThrows(), 10)
XCTAssertEqual(mock.withNamedTuple(tuple: (a: "A", b: "B")), 11)
XCTAssertEqual(mock.subclassMethod(), 12)
XCTAssertEqual(mock.withOptionalClosureAndReturn("a", closure: nil), 2)
XCTAssertEqual(mock.withOptionalClosureAndReturn("a", closure: Optional.none), 2)
XCTAssertEqual(mock.withOptionalClosureAndReturn("a", closure: { _ in }), 3)
XCTAssertEqual(mock.withClosureAndParam("a", closure: { _ in 0 }), 3)
XCTAssertEqual(mock.withMultClosures(closure: { _ in 0 }, closureB: { _ in 1 }, closureC: { _ in 2 }), 4)
XCTAssertEqual(mock.withThrowingClosure { p throws in