I am trying to write tests for my application. I would like to create a mock store, so that for example, I can override dispatch(_ action: Action) preventing my tests actually dispatching the action to my middleware, however allowing me to still assert it was called etc.
I was able to do something like this
class TestStore: Store<AppState> {
static var wasCalled = false
override func dispatch(_ action: Action) {
guard action is FetchCompany else { return }
TestStore.wasCalled = true
}
}
This however requires me to mock out my entire app state when I am setting up my TestStore which I'd prefer to avoid if possible, instead only mocking out the reducer I care about.
I thought if my store conformed to StoreType I can easily override it
var store: StoreType = Store<AppState>(reducer: appReducer, state: nil, middleware: middleware, automaticallySkipsRepeats: true)
But this just causes an error:
Protocol 'StoreType' can only be used as a generic constraint because it has Self or associated type requirements
I understand how to test my reducers and actions, but I'm really struggling to see how I can test my classes that subscribe to the store.
Why must you mock your entire app state? A little more context on the example you gave would help understand.
For this:
I understand how to test my reducers and actions, but I'm really struggling to see how I can test my classes that subscribe to the store.
Some advice I can give, is that we just call the subscription method directly newState() in many test scenarios. If you are testing subscribe/unsubscribe timing this wouldn't work.
Perhaps I'm setting up my test wrong, but I am getting the following:

Which requires me to add the state of each reducer:

An example test with this behaviour would be
```swift
func test_calls_fetch_company_on_init() {
class TestStore: Store
static var wasCalled = false
override func dispatch(_ action: Action) {
guard action is FetchCompany else { return }
TestStore.wasCalled = true
}
}
func appReducer(_ action: Action, _ state: AppState?) -> AppState {
return AppState.init(company: <#T##CompanyState#>)
}
store = TestStore(reducer: appReducer, state: nil)
XCTAssertTrue(TestStore.wasCalled)
}
```
This test would be intended to assert that my view model dispatches the correct action on init.
I appreciate your time with this 馃憤
One method I use for reducing the requirements for Mocking is to have subscribers use a generic referring to only a section of state:
(Note this is pseudo code to provide an example and might need tweaks to compile & use)
protocol HasAccounts {
var accounts: [Account] { get }
}
struct AppState {
var accounts: [Account]
}
extension AppState: HasAccounts {}
class SomeSubscriber<S: StoreType>: StoreSubscriber where S.State: HasAccounts {
init(store: S) {
store.subscribe(self) { $0.select { $0.accounts } }
}
func newState(state: [Account]) {
// ...
}
}
Then when I'm testing I can make a mock state that conforms:
class SomeSubscriberTests {
struct MockState: HasAccounts {
var accounts: [Account]
}
func testFoo() {
// create Store<MockState>, create SomeSubscriber(store: storeWithMockState)
// tests
}
}
@nodediggity
I can second that calling TheSubscriber.newState(state:) should suffice. It's the only thing I need to do in my tests, too, to see how services react to state changes. If you need to execute actual reducers, my spider senses tingle and I wonder why that is so.
As @mjarvis mentioned, you should limit your subscribers to a sub-state. If you narrow this down, it's not that painful to setup a state stub in your tests, because you don't need to create the whole AppState.
Re: replacing the dispatch function, I mentioned my "recording" store service here once: https://github.com/ReSwift/ReSwift/issues/323#issuecomment-360452118, see below for details and a current implementation
Given these types in my state module:
// In my app:
public typealias DefaultStore = Store<AppState>
/// Factory for the real store object with reducers and middleware included
public func createStore() -> DefaultStore { ... }
/// Root state type
public struct AppState {
// I'm using this instead of an empty initializer `AppState()`:
static var default: AppState { ... }
// ...
}
And these test helpers:
// In my tests
/// Store that isn't calling any reducer or middleware
func nullDefaultStore() -> DefaultStore {
return DefaultStore(
reducer: { _, _ in AppState.default },
state: nil,
middleware: [])
}
class DefaultStoreRecording {
init(store: DefaultStore = nullDefaultStore()) {
self.store = store
let originalDispatch = self.store.dispatchFunction
self.store.dispatchFunction = { action in
self.actions.append(action)
originalDispatch?(action)
}
}
let store: DefaultStore
var actions = [Action]()
var firstAction: Action? { return actions.first }
var didRecordAction: Bool { return actions.isNotEmpty }
func reset() {
actions = []
}
}
I can see how services dispatch actions like so:
func testFoo_DispatchesBar() {
let recording = DefaultStoreRecording()
let service = TheService(store: recording.store)
service.foo()
XCTAssertEqual(recording.firstAction, BarAction())
}
Maybe one of the stumbling blocks for you is that you don't inject a store instance and rely on a global variable instead?
This can be helpful if you want direct callbacks on action dispatching, e.g. when you expect middlewares to dispatch additional actions. I don't actually test middleware with this. I used this with another helper to write integration tests that interact with the UI of the app and assert on a sequence of actions being dispatched for complex workflows, so I could write:
wait(for: [storeAdapter.expect("display search results", forAction: SearchingNotesFinished.self)], timeout: 2)
The basis to forward actions directly and, not, uses a real store from production:
class ForwardingDefaultStore {
let store: DefaultStore
init(store: DefaultStore = createStore(), actionDidProcess: @escaping (_ action: ReSwift.Action) -> Void) {
self.store = store
let defaultDispatch = self.store.dispatchFunction!
self.store.dispatchFunction = { action in
defaultDispatch(action)
actionDidProcess(action)
}
}
}
@nodediggity I'm usually not one to end advice with "you're doing it wrong", so first I'll say, you're example is showing that you are testing that a side-effect has occurred in a reducer. That's not a good place to be as reducers should be pure functions. No side-effects.
If you have no choice in the matter, and just need to test it. I would go with advice already given. To see where that leads you.
My final piece of advice is that from my experience, your entire state should have some pretty sensible defaults. If you can't simply do AppState() I'd consider revisiting those defaults.
@jimisaacs I'm not sure where you are seeing a side effect in my reducer?
I was intending to assert that once my view model had loaded, it fired off an action w/ the correct type.
In the case of the test above, if the next action was not of the correct type, the test failed.
None the less, some really useful info in these replies and I have been able to make some changes in my approach that have allowed me to better test my application following all you advice.
Thank everyone, much appreciated 馃憤
If you are good to go, disregard this question. Though following up your question...
I was intending to assert that once my view model had loaded, it fired off an action w/ the correct type.
@nodediggity what actually triggers the dispatch of FetchCompany in that test example?
The only thing I see from the example is the store being initialized and provided a single reducer, which initializes some state. No middleware or view models I can see. This would normally mean that nothing is dispatched at this point. So either there is something happening outside of the test, or there is a side-effect somewhere.
@jimisaacs ah yes, I'm with you now - I'm afraid my example was probably too small to give a proper overview of what is happening.
My action is dispatched by the view model, triggered by a call from viewDidLoad.
That action is then picked up by middleware and triggers an api request.
The success or failure of that request dispatches actions that are then picked up by my reducer, populating the results or an error prop on the state.
My test was really asserting the behaviour of my view model rather than anything specific about my store setup.
Apologies, I see now it wasn't at all clear from my original post 馃憤