diff --git a/.sourcery.yml b/.sourcery.yml index 14a516c..bd7698e 100644 --- a/.sourcery.yml +++ b/.sourcery.yml @@ -1,8 +1,14 @@ sources: - ./Example/SwiftyMock/RoboKitten + # this one is needed for templates to recognize ReactiveSwift types if we want to stub them + - ./Example/Pods/ReactiveSwift/Sources templates: - ./SwiftyMock/Templates output: path: ./Example/RoboKittenTests/Mocks/Generated args: - testable: SwiftyMock_Example \ No newline at end of file + testable: SwiftyMock_Example + # hard-coding these for now until it's clear how to dynamicall get modules for used types, more here: https://github.com/krzysztofzablocki/Sourcery/issues/670 + imports: + - ReactiveSwift + - Result \ No newline at end of file diff --git a/Example/RoboKittenTests/Mocks/Generated/Mock.generated.swift b/Example/RoboKittenTests/Mocks/Generated/Mock.generated.swift index 9fe0dc2..80b47d1 100644 --- a/Example/RoboKittenTests/Mocks/Generated/Mock.generated.swift +++ b/Example/RoboKittenTests/Mocks/Generated/Mock.generated.swift @@ -1,21 +1,34 @@ -// Generated using Sourcery 0.13.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 0.14.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import Foundation import SwiftyMock +import ReactiveSwift +import Result @testable import SwiftyMock_Example class FakeLazyRoboKitten: LazyRoboKitten { - let needsRestGetCall = FunctionVoidCall() - let needsRestSetCall = FunctionCall() - var needsRest: Bool { - get { return stubCall(needsRestGetCall) } - set { stubCall(needsRestSetCall, argument: newValue) } + var needsRest: Bool + + let wantsToEatGetCall = FunctionCall() + let wantsToEatSetCall = FunctionCall() + var wantsToEat: Bool { + get { return stubCall(wantsToEatGetCall, argument: ()) } + set { stubCall(wantsToEatSetCall, argument: newValue) } + } + + init(needsRest: Bool) { + self.needsRest = needsRest } - let batteryStatusCall = FunctionVoidCall() + let sleepCall = ReactiveCall() + func sleep(hours: Int) -> SignalProducer { + return stubCall(sleepCall, argument: hours) + } + + let batteryStatusCall = FunctionCall<(), Int>() func batteryStatus() -> Int { - return stubCall(batteryStatusCall) + return stubCall(batteryStatusCall, argument: ()) } let jumpCall = FunctionCall<(x: Int, y: Int), Void>() @@ -36,9 +49,10 @@ class FakeLazyRoboKitten: LazyRoboKitten { class FakeRoboKitten: RoboKitten { - let batteryStatusCall = FunctionVoidCall() + + let batteryStatusCall = FunctionCall<(), Int>() func batteryStatus() -> Int { - return stubCall(batteryStatusCall) + return stubCall(batteryStatusCall, argument: ()) } let jumpCall = FunctionCall<(x: Int, y: Int), Void>() diff --git a/Example/SwiftyMock/RoboKitten/RoboKitten.swift b/Example/SwiftyMock/RoboKitten/RoboKitten.swift index fcd8220..d701054 100644 --- a/Example/SwiftyMock/RoboKitten/RoboKitten.swift +++ b/Example/SwiftyMock/RoboKitten/RoboKitten.swift @@ -4,6 +4,7 @@ // import Foundation +import ReactiveSwift // sourcery: Mock protocol RoboKitten { @@ -15,5 +16,9 @@ protocol RoboKitten { // sourcery: Mock protocol LazyRoboKitten: RoboKitten { + // sourcery: skipMock var needsRest: Bool { get set } -} \ No newline at end of file + var wantsToEat: Bool { get set } + + func sleep(hours: Int) -> SignalProducer +} diff --git a/README.md b/README.md index c321d86..4a6f176 100644 --- a/README.md +++ b/README.md @@ -207,3 +207,13 @@ protocol RoboKitten { ``` Third, run sourcery command `sourcery --config .sourcery.yml --watch` if you want to run service that will regenerate mocks every time your source files or templates change. Or `sourcery --config .sourcery.yml` if you want to generate mocks once. + +### Some Extras +We're able to generate templates for stubbbing and mocking `SignalProducer` from ReactiveSwift. +But, to make it work correctly, you should add path to its sources in sourcery config. + +There's one more step to have generated file compile. +Currently, we can't get all modules' names, whose types are participating in generated file. +There's [issue](https://github.com/krzysztofzablocki/Sourcery/issues/670) in Sourcery repo, that might elaborate on this task. +So, to make file compile, we need to manually (for now) import modules. Since file is always regenerated, we can do it through sourcery config file. +List all imports inside `imports` argument, as it's done here in the example `.sourcery.yml`. diff --git a/SwiftyMock/Templates/Mock.stencil b/SwiftyMock/Templates/Mock.stencil index 7a1115d..5da76fc 100644 --- a/SwiftyMock/Templates/Mock.stencil +++ b/SwiftyMock/Templates/Mock.stencil @@ -1,40 +1,58 @@ import Foundation import SwiftyMock +{# So far getting imports hardcoded from config, correct approach might be found here: https://github.com/krzysztofzablocki/Sourcery/issues/670 #} +{% for import in argument.imports %} +import {{ import }} +{% endfor %} + {% if argument.testable %}@testable import {{ argument.testable }}{% endif %} - -{% macro functionCallType method %}{% if method.parameters.count == 0 %}FunctionVoidCall{% else %}FunctionCall{% endif %}{% endmacro %} {% macro functionCallArgumentsType method %}{% if method.parameters.count == 1 %}{{ method.parameters.first.typeName.unwrappedTypeName }}{% else %}({% for param in method.parameters %}{{ param.name }}: {{ param.typeName.unwrappedTypeName }}{% if not forloop.last %}, {% endif %}{% endfor %}){% endif %}{% endmacro %} -{% macro functionCallReturnType method %}{% if not method.returnTypeName.isVoid %}{{ method.returnTypeName }}{% else %}Void{% endif %}{% endmacro %} +{% macro functionCall method %}FunctionCall<{% call functionCallArgumentsType method %}, {% if not method.returnTypeName.isVoid %}{{ method.returnTypeName }}{% else %}Void{% endif %}>{% endmacro %} +{% macro reactiveCall method %}ReactiveCall<{% call functionCallArgumentsType method %}, {% for typeParameter in method.returnTypeName.generic.typeParameters %}{{ typeParameter.typeName }}{% if not forloop.last %}, {% endif %}{% endfor %}>{% endmacro %} -{% macro stubCallArguments 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 functionCallType method %}{% if method.returnType.name == "SignalProducer" %}{% call reactiveCall method %}{% else %}{% call functionCall method %}{% endif %}{% endmacro %} -{% macro mockVariableGetterCall variable %}let {{ variable.name }}GetCall = FunctionVoidCall<{{ variable.typeName }}>(){% endmacro %} +{% macro stubCallArguments method %}, argument: {% if method.parameters.count == 0 %}(){% elif 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 %}{% endmacro %} + +{% macro mockVariableGetterCall variable %}let {{ variable.name }}GetCall = FunctionCall(){% endmacro %} {% macro mockVariableSetterCall variable %}let {{ variable.name }}SetCall = FunctionCall<{{ variable.typeName }}, Void>(){% endmacro %} {% macro mockVariable variable %} {% call mockVariableGetterCall variable %} {% if variable.isMutable %}{% call mockVariableSetterCall variable %}{% endif %} var {{ variable.name }}: {{ variable.typeName }} { - get { return stubCall({{ variable.name }}GetCall) } + get { return stubCall({{ variable.name }}GetCall, argument: ()) } {% if variable.isMutable %}set { stubCall({{ variable.name }}SetCall, argument: newValue) }{% endif %} } {% endmacro %} + +{% macro property variable %} + {% if variable.isMutable %}var{% else %}let{% endif %} {{ variable.name }}: {{ variable.typeName }} +{% endmacro %} {% for type in types.protocols where type|annotated:"Mock" %} class Fake{{ type.name }}: {{ type.name }} { {% for variable in type.allVariables|!definedInExtension %} - {% call mockVariable variable %} + {% if variable|!annotated:"skipMock" %}{% call mockVariable variable %}{% else %}{% call property variable %}{% endif %} {% if not forloop.last %} {% endif %} {% endfor %} - {% for method in type.allMethods|!definedInExtension %} - let {{ method.shortName }}Call = {% call functionCallType method %}<{% if method.parameters.count > 0 %}{% call functionCallArgumentsType method %}, {% endif %}{% call functionCallReturnType method %}>() + {% if type.allVariables|!definedInExtension|annotated:"skipMock" %} + init({% for variable in type.allVariables|!definedInExtension|annotated:"skipMock" %}{{ variable.name }}: {{ variable.typeName }}{% if not forloop.last %}, {% endif %}{% endfor %}) { + {% for variable in type.allVariables|!definedInExtension|annotated:"skipMock" %} + self.{{ variable.name }} = {{ variable.name }} + {% endfor %} + } + {% endif %} + + {% for method in type.allMethods|!definedInExtension where method|!annotated:"skipMock" %} + let {{ method.shortName }}Call = {% call functionCallType method %}() func {{ method.name }}{% if method.throws %} throws{% endif %}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} { - return stubCall({{ method.shortName }}Call{% call stubCallArguments method %}{% if method.returnTypeName.isVoid %}, defaultValue: ()){% else %}){% endif %} + return stubCall({{ method.shortName }}Call{% call stubCallArguments method %}{% if method.returnTypeName.isVoid %}, defaultValue: (){% endif %}) } {% if not forloop.last %}