Skip to content

Instantly share code, notes, and snippets.

@alexito4
Created August 4, 2020 07:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexito4/75a085b2cb54a7969c036efa65e74c3f to your computer and use it in GitHub Desktop.
Save alexito4/75a085b2cb54a7969c036efa65e74c3f to your computer and use it in GitHub Desktop.
Sourcery templates for automatic mocks from a concrete type
// swiftlint:disable line_length
// swiftlint:disable variable_name
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif
import RxSwift
@testable import WorkAngel
{% 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 %}
{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %}
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append({% for param in method.parameters %}{{ param.name }}){% endfor %}
{% else %}
{% if not method.parameters.count == 0 %}
{% call swiftifyMethodName method.selectorName %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append(({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %}))
{% endif %}
{% endif %}
{% endmacro %}
{% macro methodClosureName method %}{% call swiftifyMethodName method.selectorName %}Closure{% endmacro %}
{% macro closureReturnTypeName method %}{% if method.isOptionalReturnType %}{{ method.unwrappedReturnTypeName }}?{% else %}{{ method.returnTypeName }}{% endif %}{% endmacro %}
{% macro methodClosureDeclaration method %}
var {% call methodClosureName method %}: (({% for param in method.parameters %}{{ param.typeName }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% if method.throws %}throws {% endif %}-> {% if method.isInitializer %}Void{% else %}{% call closureReturnTypeName method %}{% endif %})?
{% endmacro %}
{% macro methodClosureCallParameters method %}{% for param in method.parameters %}{{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% 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 }}: {{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{{ ', ' if not forloop.last }}{% endfor %})?
var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations: [({% for param in method.parameters %}{{ param.name }}: {{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{{ ', ' if not forloop.last }}{% endfor %})] = []
{% endif %}
{% if not method.returnTypeName.isVoid and not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}ReturnValue: {{ '(' if method.returnTypeName.isClosure and not method.isOptionalReturnType }}{{ method.returnTypeName }}{{ ')' if method.returnTypeName.isClosure and not method.isOptionalReturnType }}{{ '!' if not method.isOptionalReturnType }}
{% endif %}
{% call methodClosureDeclaration method %}
{% if method.isInitializer %}
required {{ method.name }} {
{% call methodReceivedParameters method %}
{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
}
{% else %}
func {{ method.name }}{{ ' throws' if method.throws }}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} {
{% if method.throws %}
{% call methodThrowableErrorUsage method %}
{% endif %}
{% call swiftifyMethodName method.selectorName %}CallsCount += 1
{% call methodReceivedParameters method %}
{% if method.returnTypeName.isVoid %}
{% if method.throws %}try {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
{% else %}
return {{ 'try ' if method.throws }}{% call methodClosureName method %}.map({ {{ 'try ' if method.throws }}$0({% call methodClosureCallParameters method %}) }) ?? {% call swiftifyMethodName method.selectorName %}ReturnValue
{% 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 { return {% call underlyingMockedVariableName variable %} }
set(value) { {% 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 }} {
{% 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 %}
{# Support AutoProtocol and AutoMockable combo. Logic for vars and methods is repeated from AutoProtocol. Is done like this because sourcery won't generate the automock for something that has been genrated by autoprotol. We could add automockable and autoprotocol together but we want them in different files/targets. #}
{% for type in types.all where type.kind != "protocol" and type|annotated:"AutoMockable" and type|annotated:"AutoProtocol" %}
// AutoMockable + AutoProtocol
class {{ type.name }}Mock: {{ type.name }}Protocol {
{% for p in type.variables|!definedInExtension where p.readAccess != "private" and p.readAccess != "fileprivate" %}
{% if p.isOptional %}{% call mockOptionalVariable p %}{% elif p.isArray or p.isDictionary %}{% call mockNonOptionalArrayOrDictionaryVariable p %}{% else %}{% call mockNonOptionalVariable p %}{% endif %}
{% endfor %}
{% for m in type.methods|!definedInExtension where m.readAccess != "private" and m.readAccess != "fileprivate" and m.callName != "init" %}
{% call mockMethod m %}
{% endfor %}
}
{% endfor %}
import Foundation
import RxSwift
{% for type in types.all where type|annotated:"AutoProtocol" %}
{% set protoName %}{{ type.localName }}Protocol{% endset %}
// MARK: {{ protoName }}
{{ type.attributes.objc }} protocol {{ protoName }} {
{% for p in type.variables|!definedInExtension where p.readAccess != "private" and p.readAccess != "fileprivate" %}
var {{ p.name }}: {{ p.actualTypeName }} { get{% if p.isMutable %} set{% endif %} }
{% endfor %}
{% for m in type.methods|!definedInExtension where m.accessLevel != "private" and m.accessLevel != "fileprivate" and m.callName != "init" %}
func {{ m.name }} -> {{ m.actualReturnTypeName }}
{% endfor %}
}
extension {{ type.name }}: {{ protoName }} {}
{% endfor %}
// sourcery: AutoProtocol, AutoMockable
final class ConcreteType {
...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment