From 599716478fd29749175c0c14396a3ab4ab3d291c Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Tue, 2 Jun 2020 23:43:15 +0200 Subject: [PATCH 01/18] Simplify running Effects with Void environment --- Sources/FluxorTestSupport/EffectRunner.swift | 30 ++++++++++++++++---- Tests/FluxorTests/EffectsTests.swift | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Sources/FluxorTestSupport/EffectRunner.swift b/Sources/FluxorTestSupport/EffectRunner.swift index c50795e..ae03a01 100644 --- a/Sources/FluxorTestSupport/EffectRunner.swift +++ b/Sources/FluxorTestSupport/EffectRunner.swift @@ -9,7 +9,7 @@ import Dispatch import Fluxor import XCTest -public struct EffectRunner { +public struct EffectRunner { /** Run the `Effect` with the specified `Action` and return the published `Action`s. @@ -23,10 +23,10 @@ public struct EffectRunner { - Returns: The `Action`s published by the `Effect` if it is dispatching */ @discardableResult - public static func run(_ effect: Effect, - with action: Action, - environment: Environment, - expectedCount: Int = 1) throws -> [Action]? { + public static func run(_ effect: Effect, + with action: Action, + environment: Environment, + expectedCount: Int = 1) throws -> [Action]? { let actions = PassthroughSubject() let runDispatchingEffect = { (publisher: AnyPublisher<[Action], Never>) throws -> [Action] in let recorder = ActionRecorder(expectedNumberOfActions: expectedCount) @@ -49,6 +49,26 @@ public struct EffectRunner { } } +public extension EffectRunner where Environment == Void { + /** + Run the `Effect` with the specified `Action` and return the published `Action`s. + + The `expectedCount` defines how many `Action`s the `Publisher` should publish before they are returned. + If the `Effect` is `.nonDispatching`, the `expectedCount` is ignored. + + - Parameter effect: The `Effect` to run + - Parameter action: The `Action` to send to the `Effect` + - Parameter expectedCount: The count of `Action`s to wait for + - Returns: The `Action`s published by the `Effect` if it is dispatching + */ + @discardableResult + static func run(_ effect: Effect, + with action: Action, + expectedCount: Int = 1) throws -> [Action]? { + return try run(effect, with: action, environment: Void(), expectedCount: expectedCount) + } +} + /** The `ActionRecorder` records published `Action`s from a `Publisher`. diff --git a/Tests/FluxorTests/EffectsTests.swift b/Tests/FluxorTests/EffectsTests.swift index 4f0d50d..1136921 100644 --- a/Tests/FluxorTests/EffectsTests.swift +++ b/Tests/FluxorTests/EffectsTests.swift @@ -118,7 +118,7 @@ class EffectsTests: XCTestCase { } return publisher.eraseToAnyPublisher() } - try EffectRunner.run(effect, with: Test1Action(), environment: Void(), expectedCount: 1) + try EffectRunner.run(effect, with: Test1Action(), expectedCount: 1) XCTAssertNotNil(cancellable) } } From 452d3edd22dcf25bf417d36ce1fa13e0144903a8 Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Tue, 2 Jun 2020 23:47:56 +0200 Subject: [PATCH 02/18] Fix indentation --- Documentation/Abstracts/Test Support.md | 50 ++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Documentation/Abstracts/Test Support.md b/Documentation/Abstracts/Test Support.md index 79656cd..e5ca0cd 100644 --- a/Documentation/Abstracts/Test Support.md +++ b/Documentation/Abstracts/Test Support.md @@ -24,13 +24,13 @@ import FluxorTestSupport import XCTest class GreetingView: XCTestCase { - func testGreeting() { - let mockStore = MockStore(initialState: AppState()) - let view = GreetingView(store: mockStore) - XCTAssert(...) - mockStore.setState(AppState(greeting: "Hi Bob!")) - XCTAssert(...) - } + func testGreeting() { + let mockStore = MockStore(initialState: AppState()) + let view = GreetingView(store: mockStore) + XCTAssert(...) + mockStore.setState(AppState(greeting: "Hi Bob!")) + XCTAssert(...) + } } ``` @@ -43,13 +43,13 @@ import FluxorTestSupport import XCTest class GreetingView: XCTestCase { - func testGreeting() { - let greeting = "Hi Bob!" - let mockStore = MockStore(initialState: AppState(greeting: "Hi Steve!")) - mockStore.overrideSelector(Selectors.getGreeting, value: greeting) - let view = GreetingView(store: mockStore) - XCTAssertEqual(view.greeting, greeting) - } + func testGreeting() { + let greeting = "Hi Bob!" + let mockStore = MockStore(initialState: AppState(greeting: "Hi Steve!")) + mockStore.overrideSelector(Selectors.getGreeting, value: greeting) + let view = GreetingView(store: mockStore) + XCTAssertEqual(view.greeting, greeting) + } } ``` @@ -62,15 +62,15 @@ import FluxorTestSupport import XCTest class GreetingView: XCTestCase { - func testGreeting() { - let testInterceptor = TestInterceptor() - let store = Store(initialState: AppState()) - store.register(interceptor: self.testInterceptor) - let view = GreetingView(store: store) - XCTAssertEqual(testInteceptor.stateChanges.count, 0) - view.updateGreeting() - XCTAssertEqual(testInteceptor.stateChanges.count, 1) - } + func testGreeting() { + let testInterceptor = TestInterceptor() + let store = Store(initialState: AppState()) + store.register(interceptor: self.testInterceptor) + let view = GreetingView(store: store) + XCTAssertEqual(testInteceptor.stateChanges.count, 0) + view.updateGreeting() + XCTAssertEqual(testInteceptor.stateChanges.count, 1) + } } ``` @@ -78,7 +78,7 @@ The [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) uses this internall ## Running an `Effect` -An [`Effect`](Sources/Fluxor/Effects.swift) is inherently asynchronous, so in order to test it in a synchronous test, without a lot of boilerplate code, FluxorTestSupport comes with a` run` function that executes the [`Effect`](Sources/Fluxor/Effects.swift) with a specific [`Action`](Sources/Fluxor/Action.swift). It is possible to run both `.dispatchingOne`,` .dispatchingMultiple` and `.nonDispatching`, but the result will be different. +An [`Effect`](Sources/Fluxor/Effects.swift) is inherently asynchronous, so in order to test it in a synchronous test, without a lot of boilerplate code, FluxorTestSupport comes with a `run` function that executes the [`Effect`](Sources/Fluxor/Effects.swift) with a specific [`Action`](Sources/Fluxor/Action.swift). It is possible to run both `.dispatchingOne`,` .dispatchingMultiple` and `.nonDispatching`, but the result will be different. When running `.dispatchingOne` and` .dispatchingMultiple`, it is possible to specify the expected number of dispatched [`Action`s](Sources/Fluxor/Action.swift) and the dispatched [`Action`s](Sources/Fluxor/Action.swift) will also be returned. @@ -92,7 +92,7 @@ class SettingsEffectsTests: XCTestCase { func testSetBackground() { let effects = SettingsEffects() let action = Actions.setBackgroundColor(payload: .red) - let result = try EffectRunner.run(EffectRunnereffects.setBackgroundColor, with: action)! + let result = try EffectRunner.run(effects.setBackgroundColor, with: action)! XCTAssertEqual(result.count, 1) XCTAssertEqual(result[0], Actions.hideColorPicker()) } From 7e8c00dfbb8c6d1d1da35755e5f5b027cdd5e57d Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Tue, 2 Jun 2020 23:53:14 +0200 Subject: [PATCH 03/18] Fix docs for EffectRunner --- .jazzy.yaml | 1 + Sources/FluxorTestSupport/EffectRunner.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/.jazzy.yaml b/.jazzy.yaml index cd01b5e..78517d7 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -43,4 +43,5 @@ custom_categories: - name: Test Support children: - MockStore + - EffectRunner - TestInterceptor diff --git a/Sources/FluxorTestSupport/EffectRunner.swift b/Sources/FluxorTestSupport/EffectRunner.swift index ae03a01..96c4c04 100644 --- a/Sources/FluxorTestSupport/EffectRunner.swift +++ b/Sources/FluxorTestSupport/EffectRunner.swift @@ -9,6 +9,7 @@ import Dispatch import Fluxor import XCTest +/// The `EffectRunner` can be used to run `Effect`s with a specified `Action`. public struct EffectRunner { /** Run the `Effect` with the specified `Action` and return the published `Action`s. From 9ccbfadd7964297c3830996e7373e73f902f36ad Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Tue, 2 Jun 2020 23:58:39 +0200 Subject: [PATCH 04/18] Remove links --- Documentation/Abstracts/Test Support.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Documentation/Abstracts/Test Support.md b/Documentation/Abstracts/Test Support.md index e5ca0cd..6972e77 100644 --- a/Documentation/Abstracts/Test Support.md +++ b/Documentation/Abstracts/Test Support.md @@ -5,19 +5,19 @@ # Testing Selectors, Reducers and Effects -Every part of an application using Fluxor is highly testable. The separation of the [`Action`](Sources/Fluxor/Action.swift) (instructions), [`Selectors`](Sources/Fluxor/Selector.swift) (reading), [`Reducer`](Sources/Fluxor/Reducer.swift) (mutating) and [`Effect`](Sources/Fluxor/Effects.swift) (asynchronous) make each part decoupled, testable and easier to grasp. +Every part of an application using Fluxor is highly testable. The separation of the `Action` (instructions), `Selector`s (reading), `Reducer` (mutating) and `Effect` (asynchronous) make each part decoupled, testable and easier to grasp. -But to help out when testing components using Fluxor or asynchronous [`Effects`](Sources/Fluxor/Effects.swift), Fluxor comes with a separate package (**[FluxorTestSupport](Sources/FluxorTestSupport)**) with a [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) and extensions on [`Effect`](Sources/Fluxor/Effects.swift) to make them run syncronously. +But to help out when testing components using Fluxor or asynchronous `Effect`s, Fluxor comes with a separate package (**FluxorTestSupport**) with a `MockStore`, `TestInterceptor` and an `EffectRunner` to make `Effect`s run syncronously. -[FluxorTestSupport](Sources/FluxorTestSupport) should be linked in unit testing targets. +FluxorTestSupport should be linked in unit testing targets. ## Mocking out the `Store` -The [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) can be used to mock the [`Store`](Sources/Fluxor/Store.swift) being used. +The `MockStore` can be used to mock the `Store` being used. ### Setting a specific `State` -With [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) it is possible, from a test, to set a specific `State` to help test a specific scenario. +With `MockStore` it is possible, from a test, to set a specific `State` to help test a specific scenario. ```swift import FluxorTestSupport @@ -36,7 +36,7 @@ class GreetingView: XCTestCase { ### Overriding `Selectors` -The [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) can be used to override [`Selectors`](Sources/Fluxor/Selector.swift) so that they always return a specific value. +The `MockStore` can be used to override `Selector`s so that they always return a specific value. ```swift import FluxorTestSupport @@ -55,7 +55,7 @@ class GreetingView: XCTestCase { ## Intercepting `State` changes -The [`TestInterceptor`](Sources/FluxorTestSupport/TestInterceptor.swift) can be registered on the [`Store`](Sources/Fluxor/Store.swift). When registered it gets all [`Action`s](Sources/Fluxor/Action.swift) dispatched and `State` changes. Everything it intercepts gets saved in an array in the order received. This can be used to assert which [`Action`s](Sources/Fluxor/Action.swift) are dispatched in a test. +The `TestInterceptor` can be registered on the `Store`. When registered it gets all `Action`s dispatched and `State` changes. Everything it intercepts gets saved in an array in the order received. This can be used to assert which `Action`s are dispatched in a test. ```swift import FluxorTestSupport @@ -74,13 +74,13 @@ class GreetingView: XCTestCase { } ``` -The [`MockStore`](Sources/FluxorTestSupport/MockStore.swift) uses this internally behind the `stateChanges` property. +The `MockStore` uses this internally behind the `stateChanges` property. ## Running an `Effect` -An [`Effect`](Sources/Fluxor/Effects.swift) is inherently asynchronous, so in order to test it in a synchronous test, without a lot of boilerplate code, FluxorTestSupport comes with a `run` function that executes the [`Effect`](Sources/Fluxor/Effects.swift) with a specific [`Action`](Sources/Fluxor/Action.swift). It is possible to run both `.dispatchingOne`,` .dispatchingMultiple` and `.nonDispatching`, but the result will be different. +An `Effect` is inherently asynchronous, so in order to test it in a synchronous test, without a lot of boilerplate code, FluxorTestSupport comes with a `run` function that executes the `Effect` with a specific `Action`. It is possible to run both `.dispatchingOne`,` .dispatchingMultiple` and `.nonDispatching`, but the result will be different. -When running `.dispatchingOne` and` .dispatchingMultiple`, it is possible to specify the expected number of dispatched [`Action`s](Sources/Fluxor/Action.swift) and the dispatched [`Action`s](Sources/Fluxor/Action.swift) will also be returned. +When running `.dispatchingOne` and` .dispatchingMultiple`, it is possible to specify the expected number of dispatched `Action`s and the dispatched `Action`s will also be returned. When running `.nonDispatching`, nothing is awaited and nothing is returned. From 2b888ac248f3046df71e6eb1d74d42e81795db00 Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 3 Jun 2020 00:08:47 +0200 Subject: [PATCH 05/18] Remove logo --- Documentation/Abstracts/Test Support.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Documentation/Abstracts/Test Support.md b/Documentation/Abstracts/Test Support.md index 6972e77..aa8662b 100644 --- a/Documentation/Abstracts/Test Support.md +++ b/Documentation/Abstracts/Test Support.md @@ -1,8 +1,3 @@ -

-
- Fluxor -

- # Testing Selectors, Reducers and Effects Every part of an application using Fluxor is highly testable. The separation of the `Action` (instructions), `Selector`s (reading), `Reducer` (mutating) and `Effect` (asynchronous) make each part decoupled, testable and easier to grasp. From 611b0b5280d3196d8b72f8584541795b28609eef Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 3 Jun 2020 00:09:09 +0200 Subject: [PATCH 06/18] Remove public from WaitingError --- Sources/FluxorTestSupport/TestInterceptor.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/FluxorTestSupport/TestInterceptor.swift b/Sources/FluxorTestSupport/TestInterceptor.swift index 8f0d6ed..37bd1d7 100644 --- a/Sources/FluxorTestSupport/TestInterceptor.swift +++ b/Sources/FluxorTestSupport/TestInterceptor.swift @@ -48,8 +48,7 @@ public class TestInterceptor: Interceptor { throw WaitingError.expectedCountNotReached(message: errorMessage) } - /// Errors waiting for intercepted `Action`s - public enum WaitingError: Error { + enum WaitingError: Error { case expectedCountNotReached(message: String) } } From 3fee402c06c11a0ed4b334af40337e8c0ee52e21 Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 3 Jun 2020 00:13:13 +0200 Subject: [PATCH 07/18] Update SwiftUI.md --- Documentation/Abstracts/SwiftUI.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Documentation/Abstracts/SwiftUI.md b/Documentation/Abstracts/SwiftUI.md index 6cbf957..e38dc55 100644 --- a/Documentation/Abstracts/SwiftUI.md +++ b/Documentation/Abstracts/SwiftUI.md @@ -1,15 +1,10 @@ -

-
- Fluxor -

+# Fluxor + SwiftUI -# Using Fluxor with SwiftUI - -Fluxor is based on Combine and is therefore ideal to use with SwiftUI. It comes with a separate package (**[FluxorSwiftUI](Sources/FluxorSwiftUI)**) with extensions for [`Store`](Sources/Fluxor/Store.swift) and types that can be used directly in [SwiftUI Views](https://developer.apple.com/documentation/swiftui/view). +Fluxor is based on Combine and is therefore ideal to use with SwiftUI. It comes with a separate package (**FluxorSwiftUI**) with extensions for `Store` and types that can be used directly in [SwiftUI Views](https://developer.apple.com/documentation/swiftui/view). ## Observing a value -The [`ObservableValue`](Sources/FluxorSwiftUI/ObservableValue.swift) can be used to observe a value in the `State`. It can be created by calling the `observe` function found in the [`Store`](Sources/Fluxor/Store.swift) extension. When wrapping the [`ObservableValue`](Sources/FluxorSwiftUI/ObservableValue.swift) in an [`@ObservedObject`](https://developer.apple.com/documentation/swiftui/observedobject) property wrapper, its `current` property can be used like any other value wrapped in a [`@State`](https://developer.apple.com/documentation/swiftui/state) property wrapper. +The `ObservableValue` can be used to observe a value in the `State`. It can be created by calling the `observe` function found in the `Store` extension. When wrapping the `ObservableValue` in an [`@ObservedObject`](https://developer.apple.com/documentation/swiftui/observedobject) property wrapper, its `current` property can be used like any other value wrapped in a [`@State`](https://developer.apple.com/documentation/swiftui/state) property wrapper. ```swift import FluxorSwiftUI @@ -27,7 +22,7 @@ struct DrawView: View { ## Binding a value which can be changed -The [`ValueBinding`](Sources/FluxorSwiftUI/ValueBinding.swift) can be used to create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) to a value in the `State` and use an [`ActionTemplate`](Sources/Fluxor/Action.swift) to update the value through the [`Store`](Sources/Fluxor/Store.swift). The `binding` property on [`ValueBinding`](Sources/FluxorSwiftUI/ValueBinding.swift) will create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) which can be used like any other bindings. The value can also manually be updated using the `update` function. This will dispatch an [`Action`](Sources/Fluxor/Action.swift) on the [`Store`](Sources/Fluxor/Store.swift) based on the specified [`ActionTemplate`](Sources/Fluxor/Action.swift). +The `ValueBinding` can be used to create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) to a value in the `State` and use an `ActionTemplate` to update the value through the `Store`. The `binding` property on `ValueBinding` will create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) which can be used like any other bindings. The value can also manually be updated using the `update` function. This will dispatch an `Action` on the `Store` based on the specified `ActionTemplate`. ```swift import FluxorSwiftUI @@ -44,7 +39,7 @@ struct GreetingView: View { ## Binding a value which can be enabled and disabled -When the [`ValueBinding`](Sources/FluxorSwiftUI/ValueBinding.swift) has a `Bool` value, it can be created with a [`ActionTemplate`](Sources/Fluxor/Action.swift) for enabling the value (making it `true`) and another one for disabling the value (making it `false`). When the value is `Bool` and the payload for the [`ActionTemplate`](Sources/Fluxor/Action.swift) is either `Void` or `Bool`, the [`ValueBinding`](Sources/FluxorSwiftUI/ValueBinding.swift) gains more functions beside the `update` function to `toggle`, `enable` and `disable` the value. +When the `ValueBinding` has a `Bool` value, it can be created with a `ActionTemplate` for enabling the value (making it `true`) and another one for disabling the value (making it `false`). When the value is `Bool` and the payload for the `ActionTemplate` is either `Void` or `Bool`, the `ValueBinding` gains more functions beside the `update` function to `toggle`, `enable` and `disable` the value. ```swift import FluxorSwiftUI From 637a2fdbee5636afb8f1e0898a68b87968017d30 Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 3 Jun 2020 00:13:16 +0200 Subject: [PATCH 08/18] Update Test Support.md --- Documentation/Abstracts/Test Support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Abstracts/Test Support.md b/Documentation/Abstracts/Test Support.md index aa8662b..1b7fecd 100644 --- a/Documentation/Abstracts/Test Support.md +++ b/Documentation/Abstracts/Test Support.md @@ -1,4 +1,4 @@ -# Testing Selectors, Reducers and Effects +# Test support Every part of an application using Fluxor is highly testable. The separation of the `Action` (instructions), `Selector`s (reading), `Reducer` (mutating) and `Effect` (asynchronous) make each part decoupled, testable and easier to grasp. From 1aa7d8cfa741088d2b889ee6f3d0b052e96f923c Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 3 Jun 2020 00:16:02 +0200 Subject: [PATCH 09/18] Update SwiftUI.md --- Documentation/Abstracts/SwiftUI.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/Abstracts/SwiftUI.md b/Documentation/Abstracts/SwiftUI.md index e38dc55..d76c051 100644 --- a/Documentation/Abstracts/SwiftUI.md +++ b/Documentation/Abstracts/SwiftUI.md @@ -1,5 +1,3 @@ -# Fluxor + SwiftUI - Fluxor is based on Combine and is therefore ideal to use with SwiftUI. It comes with a separate package (**FluxorSwiftUI**) with extensions for `Store` and types that can be used directly in [SwiftUI Views](https://developer.apple.com/documentation/swiftui/view). ## Observing a value From c7b7e15346de8d465236119ffbbe95d3c4d10355 Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 3 Jun 2020 00:16:05 +0200 Subject: [PATCH 10/18] Update Test Support.md --- Documentation/Abstracts/Test Support.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/Abstracts/Test Support.md b/Documentation/Abstracts/Test Support.md index 1b7fecd..196dc6d 100644 --- a/Documentation/Abstracts/Test Support.md +++ b/Documentation/Abstracts/Test Support.md @@ -1,5 +1,3 @@ -# Test support - Every part of an application using Fluxor is highly testable. The separation of the `Action` (instructions), `Selector`s (reading), `Reducer` (mutating) and `Effect` (asynchronous) make each part decoupled, testable and easier to grasp. But to help out when testing components using Fluxor or asynchronous `Effect`s, Fluxor comes with a separate package (**FluxorTestSupport**) with a `MockStore`, `TestInterceptor` and an `EffectRunner` to make `Effect`s run syncronously. From e3e3daae0362de92f072f64995e7a0780cb24523 Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 3 Jun 2020 00:19:41 +0200 Subject: [PATCH 11/18] Update .jazzy.yaml --- .jazzy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jazzy.yaml b/.jazzy.yaml index 78517d7..aefe6d0 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -14,9 +14,9 @@ custom_categories: - name: Actions children: - Action - - IdentifiableAction - ActionTemplate - AnonymousAction + - IdentifiableAction - name: Reducers children: - Reducer From 3b23285a44ee2c6f224243fd363a8f880dad4d69 Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 3 Jun 2020 20:33:14 +0200 Subject: [PATCH 12/18] Create Testing Selectors.md --- Documentation/Guides/Testing Selectors.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Documentation/Guides/Testing Selectors.md diff --git a/Documentation/Guides/Testing Selectors.md b/Documentation/Guides/Testing Selectors.md new file mode 100644 index 0000000..0448d38 --- /dev/null +++ b/Documentation/Guides/Testing Selectors.md @@ -0,0 +1,10 @@ +# Testing `Selector`s + +In Fluxor `Selector`s are projectors of `State`. `Selector`s can be created by a `KeyPath`, by a closure or based on up to 5 other `Selector`s. +When a `Selector` is based on other `Selector`s, the projector takes the `Value`s from the others as parameters. + +The `Selector`'s `map` function takes the `State` and returns a `Value`. + +Because of the simple nature of a `Selector`, it is also simple to test them. + + From d457a27a59ff714fb5478dc1503cadd5c98f142d Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Thu, 4 Jun 2020 14:36:47 +0200 Subject: [PATCH 13/18] Fix merge --- Sources/FluxorTestSupport/EffectRunner.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FluxorTestSupport/EffectRunner.swift b/Sources/FluxorTestSupport/EffectRunner.swift index 373de04..96c4c04 100644 --- a/Sources/FluxorTestSupport/EffectRunner.swift +++ b/Sources/FluxorTestSupport/EffectRunner.swift @@ -10,7 +10,7 @@ import Fluxor import XCTest /// The `EffectRunner` can be used to run `Effect`s with a specified `Action`. -public struct EffectRunner { +public struct EffectRunner { /** Run the `Effect` with the specified `Action` and return the published `Action`s. From 75bf24040d963859f6c21eaac5d5a8e88df7888d Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Thu, 4 Jun 2020 14:38:43 +0200 Subject: [PATCH 14/18] Update Test Support.md --- Documentation/Abstracts/Test Support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Abstracts/Test Support.md b/Documentation/Abstracts/Test Support.md index a8157db..a0d9824 100644 --- a/Documentation/Abstracts/Test Support.md +++ b/Documentation/Abstracts/Test Support.md @@ -85,7 +85,7 @@ class SettingsEffectsTests: XCTestCase { func testSetBackground() { let effects = SettingsEffects() let action = Actions.setBackgroundColor(payload: .red) - let result = try EffectRunner.run(effects.setBackgroundColor, with: action, environment: AppEnvironment())! + let result = try EffectRunner.run(effects.setBackgroundColor, with: action)! XCTAssertEqual(result.count, 1) XCTAssertEqual(result[0], Actions.hideColorPicker()) } From d44cef8be07e58fb0e96f2c06a0591c8125a5b6d Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Thu, 4 Jun 2020 14:51:40 +0200 Subject: [PATCH 15/18] Update Testing Selectors.md --- Documentation/Guides/Testing Selectors.md | 35 ++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Documentation/Guides/Testing Selectors.md b/Documentation/Guides/Testing Selectors.md index 0448d38..d78d3b8 100644 --- a/Documentation/Guides/Testing Selectors.md +++ b/Documentation/Guides/Testing Selectors.md @@ -3,8 +3,41 @@ In Fluxor `Selector`s are projectors of `State`. `Selector`s can be created by a `KeyPath`, by a closure or based on up to 5 other `Selector`s. When a `Selector` is based on other `Selector`s, the projector takes the `Value`s from the others as parameters. +## Testing basic `Selector`s + The `Selector`'s `map` function takes the `State` and returns a `Value`. -Because of the simple nature of a `Selector`, it is also simple to test them. +```swift +struct Selectors { + static let getNameState = Selector(keyPath: \AppState.name) +} + +class SelectorsTests: XCTestCase { + func testGetNameState() { + // Given + let state = AppState(name: NameState(firstName: "Tim", lastName: "Cook")) + // Then + XCTAssertEqual(Selectors.getNameState.map(state), state.name) + } +} +``` + +## Testing `Selector`s based on `Selector`s + +If a `Selector` is based on the `Value`s from other `Selector`s, it will also have a `projector` property. +The `projector` can be used in tests, to easily test the `Selector` without creating the full `State` instance. +```swift +extension Selectors { + static let congratulations = Selector.with(getFullName, getBirthday) { fullName, birthday in + "Congratulations \(fullName)! Today is \(birthday.month) \(birthday.day) - your birthday!" + } +} +class SelectorsTests: XCTestCase { + func testCongratulations() { + XCTAssertEqual(Selectors.congratulations.projector("Tim Cook", Birthday(month: "November", day: "1")), + "Congratulations Tim Cook! Today is November 1 - your birthday!") + } +} +``` From 25a254cdfd3dc7a44cc41a8a657355f217f8350d Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Thu, 4 Jun 2020 21:08:59 +0200 Subject: [PATCH 16/18] Create Testing Reducers.md --- Documentation/Guides/Testing Reducers.md | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Documentation/Guides/Testing Reducers.md diff --git a/Documentation/Guides/Testing Reducers.md b/Documentation/Guides/Testing Reducers.md new file mode 100644 index 0000000..b865cdf --- /dev/null +++ b/Documentation/Guides/Testing Reducers.md @@ -0,0 +1,31 @@ +# Testing `Reducer`s + +In Fluxor `Reducer`s are basically pure functions which takes an instance of `State` and an `Action`, and returns a new `State`. +This means that given the same parameters, the `Reducer` will always return the same output. + +```swift +let appReducer = Reducer( + ReduceOn(IncrementAction.self) { state, action in + state.counter += action.value + } +) + +class ReducersTests: XCTestCase { + func testIncrementAction() { + // Given + var state = CounterState(counter: 0) + // When + appReducer.reduce(&state, IncrementAction(value: 1)) + // Then + XCTAssertEqual(state.counter, 1) + } +} + +struct CounterState { + var counter: Int +} + +struct IncrementAction: Action { + let value: Int +} +``` \ No newline at end of file From f1d8d90b202b61f2055003617977054f010e53ba Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Thu, 4 Jun 2020 21:16:29 +0200 Subject: [PATCH 17/18] Create Testing Effects.md --- Documentation/Guides/Testing Effects.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Documentation/Guides/Testing Effects.md diff --git a/Documentation/Guides/Testing Effects.md b/Documentation/Guides/Testing Effects.md new file mode 100644 index 0000000..f86b8dd --- /dev/null +++ b/Documentation/Guides/Testing Effects.md @@ -0,0 +1,22 @@ +# Testing `Effect`s + +In Fluxor `Effect`s are `Publisher`s based on the actions dispatched in the `Store`. They are inherently asynchronous, so in order to test it in a synchronous test some boilerplate code is needed. + +Fluxor comes with an `EffectRunner` (in the FluxorTestSupport package), which will run the `Effect` with a specified `Action` and waits for a given number of expected `Action`s published by the `Effect`. + +```swift +import FluxorTestSupport +import XCTest + +class SettingsEffectsTests: XCTestCase { + func testSetBackground() { + let effects = SettingsEffects() + let action = Actions.setBackgroundColor(payload: .red) + let result = try EffectRunner.run(effects.setBackgroundColor, with: action)! + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0], Actions.hideColorPicker()) + } +} +``` + +To read more about the `EffectRunner`, take a look at the documentation for [FluxorTestSupport](https://fluxor.dev/Test%20Support.html). \ No newline at end of file From e9cae4f6e2ae85a566a98337e2c0df2f0a84c47c Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Thu, 4 Jun 2020 21:16:34 +0200 Subject: [PATCH 18/18] Update .jazzy.yaml --- .jazzy.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.jazzy.yaml b/.jazzy.yaml index 2e7c57c..1083931 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -8,6 +8,11 @@ documentation: "Documentation/Guides/*.md" abstract: "Documentation/Abstracts/*.md" custom_categories: + - name: Guides + children: + - Testing Reducers + - Testing Selectors + - Testing Effects - name: Store children: - Store