Test Support

Every part of an application using Fluxor is highly testable. The separation of the Action (instructions), Selector (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, Fluxor comes with a separate package (FluxorTestSupport) with a MockStore, TestInterceptor and an EffectRunner to make Effects run syncronously.

FluxorTestSupport should only be linked in unit testing targets.

Mocking out the Store

The MockStore can be used to mock the Store being used.

Setting a specific State

With MockStore it is possible, from a test, to set a specific State to help test a specific scenario.

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(...)
    }
}

Overriding Selectors

The MockStore can be used to override Selectors so that they always return a specific value.

import FluxorTestSupport
import XCTest

class GreetingViewTests: 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)
    }
}

Intercepting state changes

NOTE: This is built into the MockStore.

The TestInterceptor can be registered on the Store. When registered it gets all Actions dispatched and state changes. Everything it intercepts gets saved in an array in the order received. This can be used to assert which Actions are dispatched in a test.

import FluxorTestSupport
import XCTest

class GreetingViewTests: XCTestCase {
    func testGreeting() {
        let testInterceptor = TestInterceptor<AppState>()
        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)
    }
}

The MockStore uses this internally behind the stateChanges property.

Running an Effect

An Effect is inherently asynchronous, so in order to test it in a synchronous test, without a lot of boilerplate code, FluxorTestSupport comes with an EffectRunner that executes the Effect with a specific Action and Environment. 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 Actions and the dispatched Actions will also be returned.

When running .nonDispatching, nothing is awaited and nothing is returned.

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())
    }
}