WIP allow non-optional values to be passed as matchers for optionals.

This commit is contained in:
Matyáš Kříž 2019-03-19 12:58:21 +01:00
parent ba434e6e91
commit 417075a94a
20 changed files with 317 additions and 122 deletions

View File

@ -78,7 +78,7 @@ public struct Generator {
private func matchableGenerics(where parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "" }
let whereClause = parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.typeWithoutAttributes))" }.joined(separator: ", ")
let whereClause = parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.type.withoutAttributes.unoptionaled.sugarized))" }.joined(separator: ", ")
return " where \(whereClause)"
}

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.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<({{method.inputTypes|genericSafe}}), {{method.returnType|genericSafe}}>{{method.parameters|matchableGenericWhere}} {
{{method.parameters|parameterMatchers}}
return cuckoo_manager.verify("{{method.fullyQualifiedName}}", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}

View File

@ -85,7 +85,12 @@ public struct Tokenizer {
}
let accessibility = (dictionary[Key.Accessibility.rawValue] as? String).flatMap { Accessibility(rawValue: $0) } ?? .Internal
let type = dictionary[Key.TypeName.rawValue] as? String
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:
@ -147,7 +152,7 @@ public struct Tokenizer {
return InstanceVariable(
name: name,
type: type ?? "__UnknownType",
type: type ?? .type("__UnknownType"),
accessibility: accessibility,
setterAccessibility: setterAccessibility,
range: range!,
@ -176,11 +181,20 @@ public struct Tokenizer {
returnSignature = " " + returnSignature
}
let returnTypeString: String
if let range = returnSignature.range(of: "->") {
returnTypeString = String(returnSignature[range.upperBound...]).trimmed
} else {
returnTypeString = "Void"
}
let returnType = WrappableType(parsing: returnTypeString)
// When bodyRange != nil, we need to create .ClassMethod instead of .ProtocolMethod
if let bodyRange = bodyRange {
return ClassMethod(
name: name,
accessibility: accessibility,
returnType: returnType,
returnSignature: returnSignature,
range: range!,
nameRange: nameRange!,
@ -191,6 +205,7 @@ public struct Tokenizer {
return ProtocolMethod(
name: name,
accessibility: accessibility,
returnType: returnType,
returnSignature: returnSignature,
range: range!,
nameRange: nameRange!,
@ -255,7 +270,9 @@ public struct Tokenizer {
isInout = false
}
return MethodParameter(label: parameterLabel, name: name, type: inoutSeparatedType, range: range!, nameRange: nameRange!, isInout: isInout)
let wrappableType = WrappableType(parsing: inoutSeparatedType)
return MethodParameter(label: parameterLabel, name: name, type: wrappableType, range: range!, nameRange: nameRange!, isInout: isInout)
default:
stderrPrint("Unknown method parameter. Dictionary: \(dictionary) \(file.path ?? "")")

View File

@ -9,6 +9,7 @@
public struct ClassMethod: Method {
public var name: String
public var accessibility: Accessibility
public var returnType: WrappableType
public var returnSignature: String
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>

View File

@ -9,6 +9,7 @@
public struct Initializer: Method, HasAccessibility {
public var name: String
public var accessibility: Accessibility
public var returnType: WrappableType
public var returnSignature: String
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>

View File

@ -8,7 +8,7 @@
public struct InstanceVariable: Token, HasAccessibility {
public var name: String
public var type: String
public var type: WrappableType
public var accessibility: Accessibility
public var setterAccessibility: Accessibility?
public var range: CountableRange<Int>
@ -33,7 +33,7 @@ public struct InstanceVariable: Token, HasAccessibility {
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

@ -9,6 +9,7 @@
public protocol Method: Token, HasAccessibility {
var name: String { get }
var accessibility: Accessibility { get }
var returnType: WrappableType { get }
var returnSignature: String { get }
var range: CountableRange<Int> { get }
var nameRange: CountableRange<Int> { get }
@ -16,6 +17,7 @@ public protocol Method: Token, HasAccessibility {
var isOptional: Bool { get }
var isOverriding: Bool { get }
var hasClosureParams: Bool { get }
var hasOptionalParams: Bool { get }
var attributes: [Attribute] { get }
}
@ -33,7 +35,7 @@ public extension Method {
}
var fullyQualifiedName: String {
let parameterTypes = parameters.map { ($0.isInout ? "inout " : "") + $0.type }
let parameterTypes = parameters.map { ($0.isInout ? "inout " : "") + $0.type.sugarized }
let nameParts = name.components(separatedBy: ":")
let lastNamePart = nameParts.last ?? ""
@ -46,20 +48,12 @@ public extension Method {
return returnSignature.trimmed.hasPrefix("throws")
}
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 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 {
@ -80,13 +74,13 @@ public extension Method {
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"
@ -109,7 +103,7 @@ public extension Method {
"parameterNames": parameters.map { $0.name }.joined(separator: ", "),
"escapingParameterNames": escapingParameterNames,
"isInit": isInit,
"returnType": returnType,
"returnType": returnType.sugarizedExplicitOnly,
"isThrowing": isThrowing,
"fullyQualifiedName": fullyQualifiedName,
"call": call,
@ -120,6 +114,7 @@ public extension Method {
"inputTypes": parameters.map { $0.typeWithoutAttributes }.joined(separator: ", "),
"isOptional": isOptional,
"hasClosureParams": hasClosureParams,
"hasOptionalParams": hasOptionalParams,
"attributes": attributes.filter { $0.isSupported },
]
}

View File

@ -9,7 +9,7 @@
public struct MethodParameter: Token, Equatable {
public var label: String?
public var name: String
public var type: String
public var type: WrappableType
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>
public var isInout: Bool
@ -23,7 +23,7 @@ public struct MethodParameter: Token, Equatable {
}
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 {
@ -34,9 +34,13 @@ public struct MethodParameter: Token, Equatable {
public var isClosure: Bool {
return typeWithoutAttributes.hasPrefix("(") && typeWithoutAttributes.range(of: "->") != nil
}
public var isOptional: Bool {
return type.isOptional
}
public var isEscaping: Bool {
return isClosure && (type.hasPrefix("@escaping") || type.hasSuffix(")?"))
return isClosure && (type.containsAttribute(named: "@escaping") || type.isOptional)
}
public func serialize() -> [String : Any] {
@ -47,6 +51,7 @@ public struct MethodParameter: Token, Equatable {
"labelAndName": labelAndName,
"typeWithoutAttributes": typeWithoutAttributes,
"isClosure": isClosure,
"isOptional": isOptional,
"isEscaping": isEscaping
]
}

View File

@ -9,6 +9,7 @@
public struct ProtocolMethod: Method {
public var name: String
public var accessibility: Accessibility
public var returnType: WrappableType
public var returnSignature: String
public var range: CountableRange<Int>
public var nameRange: CountableRange<Int>

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

@ -15,6 +15,8 @@ public protocol Matchable {
/// Matcher for this instance. This should be an equalTo type of a matcher, but it is not required.
var matcher: ParameterMatcher<MatchedType> { get }
var optionalMatcher: ParameterMatcher<MatchedType?> { get }
}
public extension Matchable {
@ -31,92 +33,61 @@ 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 Matchable where Self == MatchedType {
public var optionalMatcher: ParameterMatcher<MatchedType?> {
return Optional(self).matcher
}
}
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 Bool: Matchable {}
extension Float: Matchable {
public var matcher: ParameterMatcher<Float> {
return equal(to: self)
}
}
extension String: Matchable {}
extension Double: Matchable {
public var matcher: ParameterMatcher<Double> {
return equal(to: self)
}
}
extension Float: Matchable {}
extension Character: Matchable {
public var matcher: ParameterMatcher<Character> {
return equal(to: self)
}
}
extension Double: Matchable {}
extension Int: Matchable {
public var matcher: ParameterMatcher<Int> {
return equal(to: self)
}
}
extension Character: Matchable {}
extension Int8: Matchable {
public var matcher: ParameterMatcher<Int8> {
return equal(to: self)
}
}
extension Int: Matchable {}
extension Int16: Matchable {
public var matcher: ParameterMatcher<Int16> {
return equal(to: self)
}
}
extension Int8: Matchable {}
extension Int32: Matchable {
public var matcher: ParameterMatcher<Int32> {
return equal(to: self)
}
}
extension Int16: Matchable {}
extension Int64: Matchable {
public var matcher: ParameterMatcher<Int64> {
return equal(to: self)
}
}
extension Int32: Matchable {}
extension UInt: Matchable {
public var matcher: ParameterMatcher<UInt> {
return equal(to: self)
}
}
extension Int64: Matchable {}
extension UInt8: Matchable {
public var matcher: ParameterMatcher<UInt8> {
return equal(to: self)
}
}
extension UInt: Matchable {}
extension UInt16: Matchable {
public var matcher: ParameterMatcher<UInt16> {
return equal(to: self)
}
}
extension UInt8: Matchable {}
extension UInt32: Matchable {
public var matcher: ParameterMatcher<UInt32> {
return equal(to: self)
}
}
extension UInt16: Matchable {}
extension UInt64: Matchable {
public var matcher: ParameterMatcher<UInt64> {
return equal(to: self)
}
}
extension UInt32: Matchable {}
extension UInt64: Matchable {}

View File

@ -17,6 +17,12 @@ public struct ParameterMatcher<T>: Matchable {
public var matcher: ParameterMatcher<T> {
return self
}
public var optionalMatcher: ParameterMatcher<Optional<T>> {
return ParameterMatcher<Optional<T>> { other in
other.map(self.matchesFunction) ?? false
}
}
public func matches(_ input: T) -> Bool {
return matchesFunction(input)

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

@ -20,6 +20,12 @@ public func wrap<M: Matchable, IN>(matchable: M, mapping: @escaping (IN) -> M.Ma
}
}
public func wrap<M: Matchable, IN, O>(matchable: M, mapping: @escaping (IN) -> M.MatchedType?) -> ParameterMatcher<IN> where M.MatchedType == 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 {

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

@ -191,7 +191,7 @@ class ClassTest: XCTestCase {
}
func testInout() {
let mock = MockInoutMethod()
let mock = MockInoutMethodClass()
stub(mock) { mock in
when(mock.inoutko(param: anyInt())).then { param in
print(param)
@ -201,6 +201,23 @@ class ClassTest: XCTestCase {
var integer = 12
mock.inoutko(param: &integer)
}
func testOptionals() {
let mock = MockOptionalParamsClass()
stub(mock) { mock in
// when(mock.clashingFunction(param1: anyInt(), param2: "Henlo Fren")).thenDoNothing()
// when(mock.clashingFunction(param1: anyInt(), param2: Optional("What's the question?"))).then { print("What's 6 times 9? \($0.0)") }
// mock.function(param: "string")
when(mock.function(param: "string")).thenDoNothing()
}
// mock.clashingFunction(param1: Optional(22), param2: "Henlo Fren")
// mock.clashingFunction(param1: 42, param2: Optional("What's the question?"))
// verify(mock).clashingFunction(param1: anyInt(), param2: "Henlo Fren")
// verify(mock).clashingFunction(param1: 42, param2: Optional("What's the question?"))
}
private enum TestError: Error {
case unknown

View File

@ -223,7 +223,7 @@ class FinalFields {
}
}
class InoutMethod {
class InoutMethodClass {
func inoutko(param: inout Int) {
}
@ -236,3 +236,23 @@ class InoutMethod {
}
}
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?) {
}
}

View File

@ -145,17 +145,17 @@ class StubbingTest: XCTestCase {
when(stub.subSubMethod()).thenReturn("not nil")
// sub-class
when(stub.withImplicitlyUnwrappedOptional(i: anyInt())).thenReturn("implicit unwrapped return")
when(stub.withThrows()).thenReturn(10)
when(stub.withNamedTuple(tuple: any())).thenReturn(11)
when(stub.subclassMethod()).thenReturn(12)
when(stub.withOptionalClosureAndReturn(anyString(), closure: isNil())).thenReturn(2)
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")
when(stub.withThrowingClosureThrows(closure: anyThrowingClosure())).thenReturn("closure throwing")
when(stub.withThrowingEscapingClosure(closure: anyThrowingClosure())).thenReturn("escaping closure")
when(stub.withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())).thenReturn("optional closure throwing")
// when(stub.withImplicitlyUnwrappedOptional(i: anyInt())).thenReturn("implicit unwrapped return")
// when(stub.withThrows()).thenReturn(10)
// when(stub.withNamedTuple(tuple: any())).thenReturn(11)
// when(stub.subclassMethod()).thenReturn(12)
// when(stub.withOptionalClosureAndReturn(anyString(), closure: isNil())).thenReturn(2)
// 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")
// when(stub.withThrowingClosureThrows(closure: anyThrowingClosure())).thenReturn("closure throwing")
// when(stub.withThrowingEscapingClosure(closure: anyThrowingClosure())).thenReturn("escaping closure")
// when(stub.withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())).thenReturn("optional closure throwing")
when(stub.methodWithParameter(anyString())).thenReturn("parameter string")
when(stub.methodWithParameter(anyInt())).thenReturn("parameter int")
@ -181,9 +181,9 @@ class StubbingTest: XCTestCase {
when(stub.withEscape(anyString(), action: anyClosure())).then { _ in
callWithEscape = true
}
when(stub.withOptionalClosure(anyString(), closure: anyClosure())).then { _ in
callWithOptionalClosure = true
}
// when(stub.withOptionalClosure(anyString(), closure: anyClosure())).then { _ in
// callWithOptionalClosure = true
// }
when(stub.withLabelAndUnderscore(labelA: anyString(), anyString())).then { _ in
callWithLabelAndUnderscore = true
}
@ -266,13 +266,13 @@ class StubbingTest: XCTestCase {
verify(mock, times(1)).withThrows()
verify(mock, times(1)).withNamedTuple(tuple: any())
verify(mock, times(1)).subclassMethod()
verify(mock, times(1)).withOptionalClosureAndReturn(anyString(), closure: isNil())
// verify(mock, times(1)).withOptionalClosureAndReturn(anyString(), closure: isNil())
verify(mock, times(1)).withClosureAndParam(anyString(), closure: anyClosure())
verify(mock, times(1)).withMultClosures(closure: anyClosure(), closureB: anyClosure(), closureC: anyClosure())
verify(mock, times(1)).withThrowingClosure(closure: anyThrowingClosure())
verify(mock, times(1)).withThrowingClosureThrows(closure: anyThrowingClosure())
verify(mock, times(1)).withThrowingEscapingClosure(closure: anyThrowingClosure())
verify(mock, times(1)).withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())
// verify(mock, times(1)).withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())
verify(mock, times(1)).methodWithParameter(anyString())
verify(mock, times(1)).methodWithParameter(anyInt())