diff --git a/Example/RoboKittenTests/Common/Random.swift b/Example/RoboKittenTests/Common/Random.swift new file mode 100644 index 0000000..a09907a --- /dev/null +++ b/Example/RoboKittenTests/Common/Random.swift @@ -0,0 +1,144 @@ +import Foundation +import UIKit + +protocol Random { + static func random() -> Self +} + +extension Array: Random { + private static func randomElement() -> Element? { + guard Element.self is Random.Type else { + return nil + } + return (Element.self as? Random.Type)?.random() as? Element + } + + static func random() -> [Element] { + return (0...Int(arc4random() % 3)) + .map { _ in randomElement() } + .compactMap { $0 } + } +} + +extension Optional: Random { + private static func randomElement() -> Wrapped? { + guard Wrapped.self is Random.Type else { + return nil + } + return (Wrapped.self as? Random.Type)?.random() as? Wrapped + } + + static func random() -> Wrapped? { + return Int(arc4random() % 2) == 0 + ? nil + : randomElement() + } +} + +extension String: Random { + static func random() -> String { + let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + let len = UInt32(letters.length) + + var randomString = "" + + for _ in 0 ..< 20 { + let rand = arc4random_uniform(len) + var nextChar = letters.character(at: Int(rand)) + randomString += NSString(characters: &nextChar, length: 1) as String + } + + return randomString + } +} + +extension Int: Random { + static func random() -> Int { + return Int(arc4random() % 200) + } +} + +extension UInt: Random { + static func random() -> UInt { + return UInt(arc4random() % 200) + } +} + +extension Int32: Random { + static func random() -> Int32 { + return Int32(arc4random() % 300) + } +} + +extension Int64: Random { + static func random() -> Int64 { + return Int64(arc4random() % 300) + } +} + +extension Double: Random { + static func random() -> Double { + return Double(arc4random() % 1000) / 100 + } +} + +extension Float: Random { + static func random() -> Float { + return Float(arc4random() % 1000) / 100 + } +} + +extension Bool: Random { + static func random() -> Bool { + return arc4random() % 2 == 1 + } +} + +extension Data: Random { + static func random() -> Data { + let bytes = [UInt32](repeating: 0, count: 10).map { _ in arc4random() } + return Data(bytes: bytes, count: 10 ) + } +} + +extension Date: Random { + static func random() -> Date { + return Date(timeIntervalSince1970: Double.random()) + } +} + +extension UIImage { + static func random() -> UIImage { + let color = UIColor.random() + let rect = CGRect(origin: CGPoint(x: 0, y:0), size: CGSize(width: 1, height: 1)) + UIGraphicsBeginImageContext(rect.size) + let context = UIGraphicsGetCurrentContext()! + + context.setFillColor(color.cgColor) + context.fill(rect) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image! + } +} + +extension UIColor { + static func random() -> UIColor { + let literal = CGFloat(arc4random() % 255) + return .init(red: literal, green: literal, blue: literal, alpha: literal) + } +} + +extension NSError { + static func random() -> NSError { + return NSError(domain: .random(), code: .random(), userInfo: nil) + } +} + +extension URL: Random { + static func random() -> URL { + return URL(string: .random())! + } +} diff --git a/Example/RoboKittenTests/Templates/AutoMockable.stencil b/Example/RoboKittenTests/Templates/AutoMockable.stencil new file mode 100644 index 0000000..bc45979 --- /dev/null +++ b/Example/RoboKittenTests/Templates/AutoMockable.stencil @@ -0,0 +1,53 @@ +import Foundation +import SwiftyMock +{% if argument.testable %}@testable import {{ argument.testable }}{% endif %} + +{% macro parseFunctionType method %}{% if method.parameters.count == 0 %}FunctionVoidCall{% else %}FunctionCall{% endif %}{% endmacro %} + +{% macro defineReturn method %}{% if not method.returnTypeName.isVoid %}return {% endif %}{% endmacro %} +{% macro parseArguments method %}{% if method.parameters.count == 1 %}{{ method.parameters.first.typeName }}{% else %}({% for param in method.parameters %}{{ param.name }}: {{ param.typeName|replace:"@escaping ","" }}{% if not forloop.last %}, {% endif %}{% endfor %}){% endif %}{% endmacro %} + +{% macro parseParameterName parameter %}{{ parameter.typeAttributes }}{% endmacro %} + +{% macro parseArgumentsInMethod method %}{% if method.parameters.count > 0 %}, argument: {% if method.parameters.count == 1 %}{{ method.parameters.first.name }}{% else %}({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}){% endif %}{% endif %}{% endmacro %} + +{% macro parseReturn method %}{% if not method.returnTypeName.isVoid %}{{ method.returnTypeName }}{% else %}(){% endif %}{% endmacro %} + +{% macro mockOptionalVariable variable %} + var {{ variable.name }}: {{ variable.typeName }} +{% endmacro %} + +{% macro mockNonOptionalArrayOrDictionaryVariable variable %} + var {{ variable.name }}: {{ variable.typeName }} = {% if variable.isArray %}[]{% elif variable.isDictionary %}[:]{% endif %} +{% endmacro %} + +{% macro mockNonOptionalVariable variable %} + {% if variable.type.implements.Random or variable.type.implements.AutoRandom %} + var {{ variable.name }} = {{ variable.typeName }}.random() + {% else %} + var {{ variable.name }}: {{ variable.typeName }} { + get { return {% call underlyingMockedVariableName variable %} } + set(value) { {% call underlyingMockedVariableName variable %} = value } + } + var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }}! + {% endif %} +{% endmacro %} + +{% macro underlyingMockedVariableName variable %}underlying{{ variable.name|upperFirstLetter }}{% endmacro %} + +{% macro parseDefault method %}{% if method.returnTypeName.isVoid %}(){% else %}{% if method.returnTypeName|contains:"Signal" %}.empty{% elif method.returnType.implements.Random or method.returnType.implements.AutoRandom %}{{ method.returnTypeName }}.random(){% else %}Fake{{ method.returnTypeName }}(){%endif%}{% endif %}{% endmacro %} + +{% for type in types.protocols where type.based.AutoMock %} +final class Fake{{ type.name }}: {{ type.name }} { + {% for variable in type.allVariables|!definedInExtension %} + {% if variable.isOptional %}{% call mockOptionalVariable variable %}{% elif variable.isArray or variable.isDictionary %}{% call mockNonOptionalArrayOrDictionaryVariable variable %}{% else %}{% call mockNonOptionalVariable variable %}{% endif %} + {% endfor %} + {% for method in type.allMethods|!definedInExtension %} + + let {{ method.shortName }}Call = {% call parseFunctionType method %}<{% if method.parameters.count > 0 %}{% call parseArguments method %}, {% endif %}{% call parseReturn method %}>() + func {{ method.name }}{% if method.throws %} throws{% endif %}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} { + {% call defineReturn method %}stubCall({{ method.shortName }}Call{% call parseArgumentsInMethod method %}, defaultValue: {% call parseDefault method %}) + } + {% endfor %} +} +{% endfor %} diff --git a/Example/RoboKittenTests/Templates/AutoRandom.stencil b/Example/RoboKittenTests/Templates/AutoRandom.stencil new file mode 100644 index 0000000..d658e2b --- /dev/null +++ b/Example/RoboKittenTests/Templates/AutoRandom.stencil @@ -0,0 +1,51 @@ +import Foundation +{% if argument.testable %}@testable import {{ argument.testable }}{% endif %} + +{% macro defineDeclaration var %}{{ var.name }}:{% if var.isClosure and not var.isOptional %} @escaping{% endif %} {{ var.typeName }} = {% call defineRandom var %}{% endmacro %} +{% macro defineRandom var %}{% if var.isClosure %}{}{% else %}.random(){% endif %}{% endmacro %} + +{% for type in types.implementing.AutoRandom|!enum %} + +extension {{ type.name }}: Random { + static func random() -> {{ type.name }} { + return .restrictedRandom() + } + + static func restrictedRandom( + {% for var in type.variables %} + {% if not var.name == "hashValue" %} + {% if forloop.last %} + {% call defineDeclaration var %} + {% else %} + {% call defineDeclaration var %}, + {% endif %} + {% endif %} + {% endfor %} + ) -> {{ type.name }} { + return {{ type.name }}( + {% for var in type.variables %} + {% if not var.name == "hashValue" %} + {% if forloop.last %} + {{ var.name }}: {{ var.name }} + {% else %} + {{ var.name }}: {{ var.name }}, + {% endif %} + {% endif %} + {% endfor %} + ) + } +} +{% endfor %} + +{% for type in types.implementing.AutoRandom|enum %} +extension {{ type.name }}: Random { + static func random() -> {{ type.name }} { + switch arc4random_uniform({{ type.cases.count }}) { + {% for case in type.cases %} + case {{ forloop.counter0 }}: return .{{ case.name }}{% if case.hasAssociatedValue %}({% for associated in case.associatedValues %}{% if associated.externalName %}{{ associated.externalName }}: {% endif %}.random(){% endfor %}){% endif %} + {% endfor %} + default: return {{ .type.cases.first.name }} + } + } +} +{% endfor %}