Skip to content

Instantly share code, notes, and snippets.

@levibostian
Created March 15, 2020 18:42
Show Gist options
  • Save levibostian/146692a7b5d48828ec91ab883cb4bae7 to your computer and use it in GitHub Desktop.
Save levibostian/146692a7b5d48828ec91ab883cb4bae7 to your computer and use it in GitHub Desktop.
Sourcery advanced templates

AutoMockable

Features over Sourcery's version:

  • Support for generic function parameters and returns. You must decide on 1 type that you want to use for your generated mocks. Here, we are using String. If you want to use more then one type in your tests, you may be able to with a typealias that contains multiple different type options and set that as the generic type.
protocol KeyValueStorage: AutoMockable {
    func string(forKey key: KeyValueStorageKey) -> String?
    func setString(_ value: String?, forKey key: KeyValueStorageKey)
    // sourcery: GenericReturn = "String?"
    func obj<T: Decodable>(forKey key: KeyValueStorageKey) -> T?
    func setObj<T: Encodable>( /* sourcery: Generic = "String?" */ _ value: T?, forKey key: KeyValueStorageKey)
}
// swiftlint:disable line_length
// swiftlint:disable variable_name
import RxSwift
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif
{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %}
{% macro methodThrowableErrorDeclaration method %}
var {% call swiftifyMethodName method.selectorName %}ThrowableError: Error?
{% endmacro %}
{% macro methodThrowableErrorUsage method %}
if let error = {% call swiftifyMethodName method.selectorName %}ThrowableError {
throw error
}
{% endmacro %}
{% macro methodReceivedParameters method %}
{%if method.parameters.count == 1 %}
{% set receivedVarName %}{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}{% endfor %}{% endset %}
{{ receivedVarName }}{% for param in method.parameters %} = {{ param.name }}{% endfor %}
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append({{ receivedVarName }}!)
{% else %}
{% if not method.parameters.count == 0 %}
{% set receivedVarName %}{% call swiftifyMethodName method.selectorName %}ReceivedArguments{% endset %}
{{ receivedVarName }} = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %}){% if method.isGeneric %} as? ({% for param in method.parameters %}{{ param.name }}: {% call parameterType param %}{% if not forloop.last%}, {% endif %}{% endfor %}){% endif %}
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append({{ receivedVarName }}!)
{% endif %}
{% endif %}
{% endmacro %}
{% macro methodReturnType method %}{% if method.returnTypeName.isVoid %}Void{% elif method.annotations["GenericReturn"] %}{{ method.annotations["GenericReturn"] }}{% else %}{{ method.returnTypeName }}{% endif %}{% endmacro %}
{% macro parameterType param %}{% if param.annotations["Generic"] %}{{ param.annotations["Generic"] }}{% else %}{{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{% endif %}{% endmacro %}
{% macro parameterArguments param %}{% if param.typeAttributes.escaping %}@escaping {% endif %}{% endmacro %}
{% macro callClosure method %}return {{ 'try ' if method.throws }}{% if method.isGeneric %}({% endif %}{% call methodClosureName method %}.map({ {{ 'try ' if method.throws }}$0({% call methodClosureCallParameters method %}) }) ?? {% call swiftifyMethodName method.selectorName %}ReturnValue{% if method.isGeneric %}) as! {{ method.returnTypeName }}{% endif %}{% endmacro %}
{% macro methodClosureName method %}{% call swiftifyMethodName method.selectorName %}Closure{% endmacro %}
{% macro methodClosureDeclaration method %}
var {% call methodClosureName method %}: (({% for param in method.parameters %}{% call parameterArguments param %}{% call parameterType param %}{% if not forloop.last %}, {% endif %}{% endfor %}) {% if method.throws %}throws {% endif %}-> {% if method.isInitializer %}Void{% else %}{% call methodReturnType method %}{% endif %})?
{% endmacro %}
{% macro methodClosureCallParameters method %}{% for param in method.parameters %}{{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %}
{% macro methodClosureCallParametersFromArguments method %}{% for param in method.parameters %}{% call receivedArgumentsVarName method %}!{% if method.parameters.count > 1 %}.{{ param.name }}{% if not forloop.last %}, {% endif %}{% endif %}{% endfor %}{% endmacro %}
{% macro receivedArgumentsVarName method %}{% if method.parameters.count == 1 %}{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}{% endfor %}{% else %}{% call swiftifyMethodName method.selectorName %}ReceivedArguments{% endif %}{% endmacro %}
{% macro mockMethod method %}
//MARK: - {{ method.shortName }}
{% if method.throws %}
{% call methodThrowableErrorDeclaration method %}
{% endif %}
{% if not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}CallsCount = 0
var {% call swiftifyMethodName method.selectorName %}Called: Bool {
return {% call swiftifyMethodName method.selectorName %}CallsCount > 0
}
{% endif %}
{% if method.parameters.count == 1 %}
var {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {{ '(' if param.isClosure }}{{ param.typeName.unwrappedTypeName }}{{ ')' if param.isClosure }}?{% endfor %}
var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations{% for param in method.parameters %}: [{{ '(' if param.isClosure }}{{ param.typeName.unwrappedTypeName }}{{ ')' if param.isClosure }}{%if param.typeName.isOptional%}?{%endif%}]{% endfor %} = []
{% elif not method.parameters.count == 0 %}
var {% call swiftifyMethodName method.selectorName %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {% call parameterType param %}{{ ', ' if not forloop.last }}{% endfor %})?
var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations: [({% for param in method.parameters %}{{ param.name }}: {% call parameterType param %}{{ ', ' if not forloop.last }}{% endfor %})] = []
{% endif %}
{% if not method.returnTypeName.isVoid and not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}ReturnValue: {% call methodReturnType method %}{{ '!' if not method.isOptionalReturnType }}
{% endif %}
{% call methodClosureDeclaration method %}
{% if method.isInitializer %}
required {{ method.name }} {
{% call methodReceivedParameters method %}
{% call methodClosureName method %}?({% call methodClosureCallParametersFromArguments method %})
}
{% else %}
func {{ method.name }}{{ ' throws' if method.throws }}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} {
{% if method.throws %}
{% call methodThrowableErrorUsage method %}
{% endif %}
self.mockCalled = true
{% call swiftifyMethodName method.selectorName %}CallsCount += 1
{% call methodReceivedParameters method %}
{% if method.returnTypeName.isVoid %}
{% if method.throws %}try {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParametersFromArguments method %})
{% else %}
{% call callClosure method %}
{% endif %}
}
{% endif %}
{% endmacro %}
{% macro mockOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }}
{% endmacro %}
{% macro mockNonOptionalArrayOrDictionaryVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} = {% if variable.isArray %}[]{% elif variable.isDictionary %}[:]{% endif %}
{% endmacro %}
{% macro mockNonOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} {
get {
self.mockCalled = true
return {% call underlyingMockedVariableName variable %}
}
set(value) {
self.mockCalled = true
{% call underlyingMockedVariableName variable %} = value
}
}
var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }}!
{% endmacro %}
{% macro underlyingMockedVariableName variable %}underlying{{ variable.name|upperFirstLetter }}{% endmacro %}
{% macro mockedVariableName variable %}{{ variable.name }}{% endmacro %}
{% for type in types.protocols where type.based.AutoMockable or type|annotated:"AutoMockable" %}{% if type.name != "AutoMockable" %}
class {{ type.name }}Mock: {{ type.name }} {
var mockCalled: Bool = false // if *any* interactions done on mock. Sets/gets or methods called.
{% 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 %}
{% call mockMethod method %}
{% endfor %}
}
{% endif %}{% endfor %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment