Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
088c021cbc | |
![]() |
475322e609 | |
![]() |
9676d7e374 | |
![]() |
75a209c3ba | |
![]() |
e29f10eac6 | |
![]() |
4148a6a7ff | |
![]() |
a71ba74df1 | |
![]() |
fa66b6a0a0 | |
![]() |
6f41152d83 |
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = "Cuckoo"
|
||||
s.version = "1.10.0"
|
||||
s.version = "1.10.3"
|
||||
s.summary = "Cuckoo - first boilerplate-free Swift mocking framework."
|
||||
s.description = <<-DESC
|
||||
Cuckoo is a mocking framework with an easy to use API (inspired by Mockito).
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,18 +2,6 @@ import Foundation
|
|||
import Stencil
|
||||
|
||||
public struct Generator {
|
||||
|
||||
private static let reservedKeywordsNotAllowedAsMethodName: Set = [
|
||||
// Keywords used in declarations:
|
||||
"associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "operator", "private", "precedencegroup", "protocol", "public", "rethrows", "static", "struct", "subscript", "typealias", "var",
|
||||
// Keywords used in statements:
|
||||
"break", "case", "catch", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "throw", "switch", "where", "while",
|
||||
// Keywords used in expressions and types:
|
||||
"Any", "as", "catch", "false", "is", "nil", "rethrows", "self", "super", "throw", "throws", "true", "try",
|
||||
// Keywords used in patterns:
|
||||
"_",
|
||||
]
|
||||
|
||||
private let declarations: [Token]
|
||||
|
||||
public init(file: FileRepresentation) {
|
||||
|
@ -56,10 +44,10 @@ public struct Generator {
|
|||
guard let parameters = value as? [MethodParameter] else { return value }
|
||||
return self.closeNestedClosure(for: parameters)
|
||||
}
|
||||
|
||||
|
||||
ext.registerFilter("escapeReservedKeywords") { (value: Any?) in
|
||||
guard let name = value as? String else { return value }
|
||||
return self.escapeReservedKeywords(for: name)
|
||||
return escapeReservedKeywords(for: name)
|
||||
}
|
||||
|
||||
ext.registerFilter("removeClosureArgumentNames") { (value: Any?) in
|
||||
|
@ -110,7 +98,13 @@ public struct Generator {
|
|||
guard parameters.isEmpty == false else { return "let matchers: [Cuckoo.ParameterMatcher<Void>] = []" }
|
||||
|
||||
let tupleType = parameters.map { $0.typeWithoutAttributes }.joined(separator: ", ")
|
||||
let matchers = parameters.enumerated().map { "wrap(matchable: \($1.name)) { $0\(parameters.count > 1 ? ".\($0)" : "") }" }.joined(separator: ", ")
|
||||
|
||||
let matchers = parameters.enumerated().map { index, parameter in
|
||||
let name = escapeReservedKeywords(for: parameter.name)
|
||||
return "wrap(matchable: \(name)) { $0\(parameters.count > 1 ? ".\(index)" : "") }"
|
||||
}
|
||||
.joined(separator: ", ")
|
||||
|
||||
return "let matchers: [Cuckoo.ParameterMatcher<(\(genericSafeType(from: tupleType)))>] = [\(matchers)]"
|
||||
}
|
||||
|
||||
|
@ -151,10 +145,6 @@ public struct Generator {
|
|||
}
|
||||
return fullString
|
||||
}
|
||||
|
||||
private func escapeReservedKeywords(for name: String) -> String {
|
||||
Self.reservedKeywordsNotAllowedAsMethodName.contains(name) ? "`\(name)`" : name
|
||||
}
|
||||
|
||||
private func removeClosureArgumentNames(for type: String) -> String {
|
||||
type.replacingOccurrences(
|
||||
|
|
|
@ -60,15 +60,15 @@ extension {{ container.parentFullyQualifiedName }} {
|
|||
{{ attribute.text }}
|
||||
{% endfor %}
|
||||
{{ property.accessibility }}{% if container.isImplementation %} override{% endif %} var {{ property.name }}: {{ property.type }} {
|
||||
get {
|
||||
return cuckoo_manager.getter("{{ property.name }}",
|
||||
get{% if property.isAsync %} async{% endif %}{% if property.isThrowing %} throws{% endif %} {
|
||||
return {% if property.isThrowing %}try {% endif %}{% if property.isAsync %}await {% endif %}cuckoo_manager.getter{% if property.isThrowing %}Throws{% endif %}("{{ property.name }}",
|
||||
superclassCall:
|
||||
{% if container.isImplementation %}
|
||||
super.{{ property.name }}
|
||||
{% if property.isThrowing %}try {% endif %}{% if property.isAsync %}await {% endif %}super.{{ property.name }}
|
||||
{% else %}
|
||||
Cuckoo.MockManager.crashOnProtocolSuperclassCall()
|
||||
{% endif %},
|
||||
defaultCall: __defaultImplStub!.{{property.name}})
|
||||
defaultCall: {% if property.isThrowing %}try {% endif %}{% if property.isAsync %}await {% endif %} __defaultImplStub!.{{property.name}})
|
||||
}
|
||||
{% ifnot property.isReadOnly %}
|
||||
set {
|
||||
|
|
|
@ -185,11 +185,19 @@ public struct Tokenizer {
|
|||
guessedType = type
|
||||
}
|
||||
|
||||
let effects: InstanceVariable.Effects
|
||||
if let bodyRange = bodyRange {
|
||||
effects = parseEffects(source: source.utf8[bodyRange])
|
||||
} else {
|
||||
effects = .init()
|
||||
}
|
||||
|
||||
return InstanceVariable(
|
||||
name: name,
|
||||
type: guessedType ?? .type("__UnknownType"),
|
||||
accessibility: accessibility,
|
||||
setterAccessibility: setterAccessibility,
|
||||
effects: effects,
|
||||
range: range!,
|
||||
nameRange: nameRange!,
|
||||
overriding: false,
|
||||
|
@ -518,6 +526,34 @@ public struct Tokenizer {
|
|||
return ReturnSignature(isAsync: isAsync, throwString: throwString, returnType: returnType ?? WrappableType.type("Void"), whereConstraints: whereConstraints)
|
||||
}
|
||||
|
||||
private func parseEffects(source: String) -> InstanceVariable.Effects {
|
||||
var effects = InstanceVariable.Effects()
|
||||
|
||||
let trimmed = source.drop(while: { $0.isWhitespace })
|
||||
guard trimmed.hasPrefix("get") else { return effects }
|
||||
|
||||
let afterGet = trimmed.dropFirst("get".count).drop(while: { $0.isWhitespace })
|
||||
var index = afterGet.startIndex
|
||||
parseLoop: while index != afterGet.endIndex {
|
||||
let character = afterGet[index]
|
||||
switch character {
|
||||
case "a":
|
||||
effects.isAsync = true
|
||||
index = source.index(index, offsetBy: "async".count)
|
||||
case "t":
|
||||
effects.isThrowing = true
|
||||
index = source.index(index, offsetBy: "throws".count)
|
||||
case let c where c.isWhitespace:
|
||||
break
|
||||
default:
|
||||
break parseLoop
|
||||
}
|
||||
index = source.index(after: index)
|
||||
}
|
||||
|
||||
return effects
|
||||
}
|
||||
|
||||
// FIXME: Remove when SourceKitten fixes the off-by-one error that includes the ending `>` in the last inherited type.
|
||||
private func fixSourceKittenLastGenericParameterBug(_ genericParameters: [GenericParameter]) -> [GenericParameter] {
|
||||
let fixedGenericParameters: [GenericParameter]
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
public struct InstanceVariable: Token, HasAccessibility, HasAttributes {
|
||||
public struct Effects {
|
||||
public var isThrowing = false
|
||||
public var isAsync = false
|
||||
}
|
||||
|
||||
public var name: String
|
||||
public var type: WrappableType
|
||||
public var accessibility: Accessibility
|
||||
public var setterAccessibility: Accessibility?
|
||||
public var effects: Effects
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var overriding: Bool
|
||||
|
@ -22,8 +28,10 @@ public struct InstanceVariable: Token, HasAccessibility, HasAttributes {
|
|||
}
|
||||
|
||||
public func serialize() -> [String : Any] {
|
||||
let readOnlyString = readOnly ? "ReadOnly" : ""
|
||||
let readOnlyVerifyString = readOnly ? "ReadOnly" : ""
|
||||
let readOnlyStubString = effects.isThrowing ? "" : readOnlyVerifyString
|
||||
let optionalString = type.isOptional && !readOnly ? "Optional" : ""
|
||||
let throwingString = effects.isThrowing ? "Throwing" : ""
|
||||
|
||||
return [
|
||||
"name": name,
|
||||
|
@ -31,8 +39,10 @@ public struct InstanceVariable: Token, HasAccessibility, HasAttributes {
|
|||
"nonOptionalType": type.unoptionaled.sugarized,
|
||||
"accessibility": accessibility.sourceName,
|
||||
"isReadOnly": readOnly,
|
||||
"stubType": (overriding ? "Class" : "Protocol") + "ToBeStubbed\(readOnlyString)\(optionalString)Property",
|
||||
"verifyType": "Verify\(readOnlyString)\(optionalString)Property",
|
||||
"isAsync": effects.isAsync,
|
||||
"isThrowing": effects.isThrowing,
|
||||
"stubType": (overriding ? "Class" : "Protocol") + "ToBeStubbed\(readOnlyStubString)\(optionalString)\(throwingString)Property",
|
||||
"verifyType": "Verify\(readOnlyVerifyString)\(optionalString)Property",
|
||||
"attributes": attributes.filter { $0.isSupported },
|
||||
"hasUnavailablePlatforms": hasUnavailablePlatforms,
|
||||
"unavailablePlatformsCheck": unavailablePlatformsCheck,
|
||||
|
|
|
@ -38,11 +38,11 @@ public extension Method {
|
|||
.map { $0 + ": " + $1 }
|
||||
.joined(separator: ", ") + lastNamePart + returnSignatureString
|
||||
}
|
||||
|
||||
|
||||
var isAsync: Bool {
|
||||
return returnSignature.isAsync
|
||||
}
|
||||
|
||||
|
||||
var isThrowing: Bool {
|
||||
guard let throwType = returnSignature.throwType else { return false }
|
||||
return throwType.isThrowing || throwType.isRethrowing
|
||||
|
@ -67,7 +67,9 @@ public extension Method {
|
|||
|
||||
func serialize() -> [String : Any] {
|
||||
let call = parameters.map {
|
||||
let referencedName = "\($0.isInout ? "&" : "")\($0.name)\($0.isAutoClosure ? "()" : "")"
|
||||
let name = escapeReservedKeywords(for: $0.name)
|
||||
let referencedName = "\($0.isInout ? "&" : "")\(name)\($0.isAutoClosure ? "()" : "")"
|
||||
|
||||
if let label = $0.label {
|
||||
return "\(label): \(referencedName)"
|
||||
} else {
|
||||
|
@ -95,7 +97,7 @@ public extension Method {
|
|||
}
|
||||
return "{ \(parameterSignature)\(returnSignature) in fatalError(\"This is a stub! It's not supposed to be called!\") }"
|
||||
} else {
|
||||
return parameter.name
|
||||
return escapeReservedKeywords(for: parameter.name)
|
||||
}
|
||||
}.joined(separator: ", ")
|
||||
|
||||
|
@ -108,7 +110,7 @@ public extension Method {
|
|||
"accessibility": accessibility.sourceName,
|
||||
"returnSignature": returnSignature.description,
|
||||
"parameters": parameters,
|
||||
"parameterNames": parameters.map { $0.name }.joined(separator: ", "),
|
||||
"parameterNames": parameters.map { escapeReservedKeywords(for: $0.name) }.joined(separator: ", "),
|
||||
"escapingParameterNames": escapingParameterNames,
|
||||
"isInit": isInit,
|
||||
"returnType": returnType.explicitOptionalOnly.sugarized,
|
||||
|
|
|
@ -3,6 +3,8 @@ import Foundation
|
|||
struct TypeGuesser {
|
||||
static func guessType(from value: String) -> String? {
|
||||
let value = value.trimmed
|
||||
guard !value.isEmpty else { return nil }
|
||||
|
||||
let casting = checkCasting(from: value)
|
||||
guard casting == nil else { return casting }
|
||||
|
||||
|
|
|
@ -48,6 +48,26 @@ extension Sequence {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reserved keywords that are not allowed as function names, function parameters, or local variables, etc.
|
||||
fileprivate let reservedKeywordsNotAllowed: Set = [
|
||||
// Keywords used in declarations:
|
||||
"associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout",
|
||||
"internal", "let", "operator", "private", "precedencegroup", "protocol", "public", "rethrows", "static",
|
||||
"struct", "subscript", "typealias", "var",
|
||||
// Keywords used in statements:
|
||||
"break", "case", "catch", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in",
|
||||
"repeat", "return", "throw", "switch", "where", "while",
|
||||
// Keywords used in expressions and types:
|
||||
"Any", "as", "catch", "false", "is", "nil", "rethrows", "self", "super", "throw", "throws", "true", "try",
|
||||
// Keywords used in patterns:
|
||||
"_",
|
||||
]
|
||||
|
||||
/// Utility function for escaping reserved keywords for a symbol name.
|
||||
internal func escapeReservedKeywords(for name: String) -> String {
|
||||
reservedKeywordsNotAllowed.contains(name) ? "`\(name)`" : name
|
||||
}
|
||||
|
||||
internal func extractRange(from dictionary: [String: SourceKitRepresentable], offset: Key, length: Key) -> CountableRange<Int>? {
|
||||
guard let offset = (dictionary[offset.rawValue] as? Int64).map(Int.init),
|
||||
let length = (dictionary[length.rawValue] as? Int64).map(Int.init) else { return nil }
|
||||
|
|
10
README.md
10
README.md
|
@ -39,9 +39,9 @@ Due to the limitations mentioned above, unoverridable code structures are not su
|
|||
## Requirements
|
||||
Cuckoo works on the following platforms:
|
||||
|
||||
- **iOS 8+**
|
||||
- **Mac OSX 10.9+**
|
||||
- **tvOS 9+**
|
||||
- **iOS 11+**
|
||||
- **Mac OSX 10.13+**
|
||||
- **tvOS 11+**
|
||||
|
||||
**watchOS** support is not yet possible due to missing XCTest library.
|
||||
|
||||
|
@ -102,7 +102,9 @@ Note: All paths in the Run script must be absolute. Variable `PROJECT_DIR` autom
|
|||
|
||||
1. In Xcode, navigate in menu: File > Swift Packages > Add Package Dependency
|
||||
2. Add `https://github.com/Brightify/Cuckoo.git`
|
||||
3. Select "Up to Next Major" with `1.9.1`
|
||||
3. For the Dependency Rule, Select "Up to Next Major" with `1.9.1`. Click Add Package.
|
||||
4. On the 'Choose Package Products for Cuckoo' dialog, under 'Add to Target', please ensure you select your Test target as it will not compile on the app target.
|
||||
5. Click Add Package.
|
||||
|
||||
Cuckoo relies on a script that is currently not downloadable using SPM. However, for convenience, you can copy this line into the terminal to download the latest `run` script. If the `run` script changes in the future, you'll need to execute this command again.
|
||||
```Bash
|
||||
|
|
|
@ -310,11 +310,27 @@ extension MockManager {
|
|||
return call(getterName(property), parameters: Void(), escapingParameters: Void(), superclassCall: superclassCall(), defaultCall: defaultCall())
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public func getter<T>(_ property: String, superclassCall: @autoclosure () async -> T, defaultCall: @autoclosure () async -> T) async -> T {
|
||||
return await call(getterName(property), parameters: Void(), escapingParameters: Void(), superclassCall: await superclassCall(), defaultCall: await defaultCall())
|
||||
}
|
||||
|
||||
public func setter<T>(_ property: String, value: T, superclassCall: @autoclosure () -> Void, defaultCall: @autoclosure () -> Void) {
|
||||
return call(setterName(property), parameters: value, escapingParameters: value, superclassCall: superclassCall(), defaultCall: defaultCall())
|
||||
}
|
||||
}
|
||||
|
||||
extension MockManager {
|
||||
public func getterThrows<T>(_ property: String, superclassCall: @autoclosure () throws -> T, defaultCall: @autoclosure () throws -> T) throws -> T {
|
||||
return try callThrows(getterName(property), parameters: Void(), escapingParameters: Void(), superclassCall: try superclassCall(), defaultCall: try defaultCall())
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public func getterThrows<T>(_ property: String, superclassCall: @autoclosure () async throws -> T, defaultCall: @autoclosure () async throws -> T) async throws -> T {
|
||||
return try await callThrows(getterName(property), parameters: Void(), escapingParameters: Void(), superclassCall: try await superclassCall(), defaultCall: try await defaultCall())
|
||||
}
|
||||
}
|
||||
|
||||
extension MockManager {
|
||||
public func call<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: @autoclosure () -> OUT, defaultCall: @autoclosure () -> OUT) -> OUT {
|
||||
return callInternal(method, parameters: parameters, escapingParameters: escapingParameters, superclassCall: superclassCall, defaultCall: defaultCall)
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// ToBeStubbedThrowingProperty.swift
|
||||
// Cuckoo
|
||||
//
|
||||
// Created by Kabir Oberai on 2023-03-27.
|
||||
//
|
||||
|
||||
public protocol ToBeStubbedThrowingProperty {
|
||||
associatedtype GetterType: StubThrowingFunction
|
||||
|
||||
var get: GetterType { get }
|
||||
}
|
||||
|
||||
public struct ProtocolToBeStubbedThrowingProperty<MOCK: ProtocolMock, T>: ToBeStubbedThrowingProperty {
|
||||
private let manager: MockManager
|
||||
private let name: String
|
||||
|
||||
public var get: ProtocolStubThrowingFunction<Void, T> {
|
||||
return ProtocolStubThrowingFunction(stub:
|
||||
manager.createStub(for: MOCK.self, method: getterName(name), parameterMatchers: []))
|
||||
}
|
||||
|
||||
public init(manager: MockManager, name: String) {
|
||||
self.manager = manager
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
public struct ClassToBeStubbedThrowingProperty<MOCK: ClassMock, T>: ToBeStubbedThrowingProperty {
|
||||
private let manager: MockManager
|
||||
private let name: String
|
||||
|
||||
public var get: ClassStubThrowingFunction<Void, T> {
|
||||
return ClassStubThrowingFunction(stub:
|
||||
manager.createStub(for: MOCK.self, method: getterName(name), parameterMatchers: []))
|
||||
}
|
||||
|
||||
public init(manager: MockManager, name: String) {
|
||||
self.manager = manager
|
||||
self.name = name
|
||||
}
|
||||
}
|
|
@ -58,6 +58,61 @@ class ProtocolTest: XCTestCase {
|
|||
verify(mock).optionalProperty.set(equal(to: 0))
|
||||
}
|
||||
|
||||
func testThrowingProperty() {
|
||||
stub(mock) { mock in
|
||||
when(mock.throwsProperty.get).thenReturn(5)
|
||||
}
|
||||
|
||||
XCTAssertEqual(try mock.throwsProperty, 5)
|
||||
verify(mock).throwsProperty.get()
|
||||
|
||||
clearInvocations(mock)
|
||||
|
||||
stub(mock) { mock in
|
||||
when(mock.throwsProperty.get).thenThrow(TestError.unknown)
|
||||
}
|
||||
|
||||
XCTAssertThrowsError(try mock.throwsProperty)
|
||||
|
||||
verify(mock).throwsProperty.get()
|
||||
}
|
||||
|
||||
func testAsyncProperty() async {
|
||||
stub(mock) { mock in
|
||||
when(mock.asyncProperty.get).thenReturn(5)
|
||||
}
|
||||
|
||||
let result = await mock.asyncProperty
|
||||
XCTAssertEqual(result, 5)
|
||||
verify(mock).asyncProperty.get()
|
||||
}
|
||||
|
||||
func testAsyncThrowingProperty() async {
|
||||
stub(mock) { mock in
|
||||
when(mock.asyncThrowsProperty.get).thenReturn(5)
|
||||
}
|
||||
|
||||
let result = try! await mock.asyncThrowsProperty
|
||||
XCTAssertEqual(result, 5)
|
||||
verify(mock).asyncThrowsProperty.get()
|
||||
|
||||
clearInvocations(mock)
|
||||
|
||||
stub(mock) { mock in
|
||||
when(mock.asyncThrowsProperty.get).thenThrow(TestError.unknown)
|
||||
}
|
||||
|
||||
var threw = false
|
||||
do {
|
||||
_ = try await mock.asyncThrowsProperty
|
||||
} catch {
|
||||
threw = true
|
||||
}
|
||||
|
||||
XCTAssertTrue(threw)
|
||||
verify(mock).asyncThrowsProperty.get()
|
||||
}
|
||||
|
||||
func testNoReturn() {
|
||||
var called = false
|
||||
stub(mock) { mock in
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// PropertyWrappers.swift
|
||||
// Cuckoo
|
||||
//
|
||||
// Created by Kabir Oberai on 2023-03-28.
|
||||
//
|
||||
|
||||
// wrappers without annotations aren't supported but their
|
||||
// existence shouldn't cause the generator to crash
|
||||
|
||||
@propertyWrapper
|
||||
struct DoublingWrapper {
|
||||
private var backing: Int
|
||||
var wrappedValue: Int {
|
||||
get { backing * 2 }
|
||||
set { backing = newValue }
|
||||
}
|
||||
|
||||
init(wrappedValue: Int) {
|
||||
self.backing = wrappedValue
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backing = 0
|
||||
}
|
||||
}
|
||||
|
||||
struct Wrappers {
|
||||
@DoublingWrapper var base
|
||||
@DoublingWrapper var withValue = 2
|
||||
@DoublingWrapper var withAnnotation: Int
|
||||
@DoublingWrapper var withAnnotationAndValue: Int = 2
|
||||
}
|
|
@ -27,6 +27,11 @@ class TestedClass {
|
|||
return "a"
|
||||
}
|
||||
|
||||
var readOnlyPropertyWithJapaneseComment: String {
|
||||
// 日本語コメント
|
||||
return "a"
|
||||
}
|
||||
|
||||
@available(iOS 42.0, *)
|
||||
var unavailableProperty: UnavailableProtocol? {
|
||||
return nil
|
||||
|
@ -53,6 +58,18 @@ class TestedClass {
|
|||
) -> String
|
||||
) -> () = { i in }
|
||||
|
||||
var asyncProperty: Int {
|
||||
get async { 0 }
|
||||
}
|
||||
|
||||
var asyncThrowsProperty: Int {
|
||||
get async throws { 0 }
|
||||
}
|
||||
|
||||
var throwsProperty: Int {
|
||||
get throws { 0 }
|
||||
}
|
||||
|
||||
func noReturn() {
|
||||
}
|
||||
|
||||
|
@ -148,6 +165,10 @@ class TestedClass {
|
|||
func withLabelAndUnderscore(labelA a: String, _ b: String) {
|
||||
}
|
||||
|
||||
func withReservedKeywords(for: String, in: String) -> String {
|
||||
"hello"
|
||||
}
|
||||
|
||||
func callingCountCharactersMethodWithHello() -> Int {
|
||||
return count(characters: "Hello")
|
||||
}
|
||||
|
|
|
@ -34,6 +34,14 @@ protocol TestedProtocol {
|
|||
) -> String
|
||||
) -> () { get set }
|
||||
|
||||
var throwsProperty: Int { get throws }
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
var asyncProperty: Int { get async }
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
var asyncThrowsProperty: Int { get async throws }
|
||||
|
||||
func noReturn()
|
||||
|
||||
func count(characters: String) -> Int
|
||||
|
@ -56,6 +64,14 @@ protocol TestedProtocol {
|
|||
|
||||
func withLabelAndUnderscore(labelA a: String, _ b: String)
|
||||
|
||||
/// In this example `for` and `in` are not actually used in a way that conflicts with reserved keywords because
|
||||
/// conforming types will typically use `for` and `in` as an argument label for parameter with a different name,
|
||||
/// thus avoiding the usage of a reserved keyword in the body of the function.
|
||||
///
|
||||
/// The problem was with the generated mock code, which was in turn using these in the body without escaping them,
|
||||
/// causing the generated mock code to fail to compile.
|
||||
func withReservedKeywords(for: String, in: String) -> String
|
||||
|
||||
func withNamedTuple(tuple: (a: String, b: String)) -> Int
|
||||
|
||||
func withImplicitlyUnwrappedOptional(i: Int!) -> String
|
||||
|
|
|
@ -124,6 +124,55 @@ class StubbingTest: XCTestCase {
|
|||
|
||||
XCTAssertEqual(mock.protocolMethod(), "a1")
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
func testEffectfulProps() async {
|
||||
let mock = MockTestedSubSubClass()
|
||||
|
||||
XCTAssertNotNil(mock)
|
||||
|
||||
stub(mock) { stub in
|
||||
when(stub.asyncProperty.get).thenReturn(5)
|
||||
when(stub.throwsProperty.get).thenReturn(6)
|
||||
when(stub.asyncThrowsProperty.get).thenReturn(7)
|
||||
}
|
||||
|
||||
let resultAsync = await mock.asyncProperty
|
||||
XCTAssertEqual(resultAsync, 5)
|
||||
|
||||
let resultThrows = try! mock.throwsProperty
|
||||
XCTAssertEqual(resultThrows, 6)
|
||||
|
||||
let resultAsyncThrows = try! await mock.asyncThrowsProperty
|
||||
XCTAssertEqual(resultAsyncThrows, 7)
|
||||
|
||||
verify(mock, times(1)).asyncProperty.get()
|
||||
verify(mock, times(1)).throwsProperty.get()
|
||||
verify(mock, times(1)).asyncThrowsProperty.get()
|
||||
|
||||
enum TestError: Error { case fromThrows, fromAsyncThrows }
|
||||
|
||||
stub(mock) { stub in
|
||||
when(stub.throwsProperty.get).thenThrow(TestError.fromThrows)
|
||||
when(stub.asyncThrowsProperty.get).thenThrow(TestError.fromAsyncThrows)
|
||||
}
|
||||
|
||||
var caughtFromThrows = false
|
||||
do {
|
||||
_ = try mock.throwsProperty
|
||||
} catch TestError.fromThrows {
|
||||
caughtFromThrows = true
|
||||
} catch {}
|
||||
XCTAssert(caughtFromThrows)
|
||||
|
||||
var caughtFromAsyncThrows = false
|
||||
do {
|
||||
_ = try await mock.asyncThrowsProperty
|
||||
} catch TestError.fromAsyncThrows {
|
||||
caughtFromAsyncThrows = true
|
||||
} catch {}
|
||||
XCTAssert(caughtFromAsyncThrows)
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
func testAsyncMethods() async {
|
||||
|
|
|
@ -19,11 +19,11 @@ public enum PlatformType: String {
|
|||
public var libraryDeploymentTarget: DeploymentTarget {
|
||||
switch self {
|
||||
case .iOS:
|
||||
return .iOS(targetVersion: "8.0", devices: [.iphone, .ipad])
|
||||
return .iOS(targetVersion: "11.0", devices: [.iphone, .ipad])
|
||||
case .macOS:
|
||||
return .macOS(targetVersion: "10.9")
|
||||
return .macOS(targetVersion: "10.13")
|
||||
case .tvOS:
|
||||
return .tvOS(targetVersion: "9.0")
|
||||
return .tvOS(targetVersion: "11.0")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue