Hi, I am struggling with testing for dispatched action.
First, I Make TestStore that caching dispatched actions.
class TestStore: DispatchingStoreType {
let actionSubject = PublishSubject<Action>()
func dispatch(_ action: Action) {
actionSubject.onNext(action)
}
func onActionDispatched() -> Observable<Action> {
return actionSubject
}
}
Second, caching dispatched actions using RxTest
let actionBuffer = TestScheduler(initialClock: 0).createObserver(Action.self)
testStore.onActionDispatched()
.subscribe(actionBuffer)
Then, I want to check the result like:
let expectations = [
Recorded.next(0, AAction()),
Recorded.next(0, BAction())
]
XCTAssertEqual(actionBuffer.events, expectations)
But, when different Actions dispatched, I have no idea how to make Equatable for those Actions.
Any help for me??
Or how do you guys test in this situation ?
Side question: Where does onActionDispatched() come from?
I am doing it in a similar way, only without Rx. Most of my actions use simple property types, so I get Equatable conformance for free. If not, well, I write conformance myself.
public struct SelectingNotes: ReSwift.Action, Equatable, CustomStringConvertible {
public let selection: String
public init(selection: String) {
self.selection = selection
}
}
public struct CompletingNoteSelection: ReSwift.Action, Equatable {
public init() { }
}
It may look weird that CompletingNoteSelection is equatable even though it has no content. But then I can use XCTAssertEqual checks anyway instead of having to sometimes fall back to XCTAssert(action is CompletingNoteSelection) or similar.
I record all actions dispatched to the store by wrapping the Store's dispatchFunction:
class MyDefaultStoreRecording {
init(store: MyDefaultStore = createMyDefaultStoreWithoutMiddleware()) {
self.store = store
let originalDispatch = self.store.dispatchFunction
self.store.dispatchFunction = { action in
self.actions.append(action)
originalDispatch?(action)
}
}
let store: MyDefaultStore
var actions = [Action]()
var firstAction: Action? { return actions.first }
var didRecordAction: Bool { return actions.isNotEmpty }
func reset() {
actions = []
}
}
In tests, I thus have stuff like this:
func testNewState_SelectsNote() {
let storeRecording = MyDefaultStoreRecording()
let someService = SomeService(store: storeRecording.store)
let title = "the title"
someService.doSomethingToSelect(title: title)
XCTAssertEqual(storeRecording.actions.count, 1)
XCTAssertEqual(storeRecording.actions.first as? SelectingNotes,
SelectingNotes(selection: title))
}
I would go with a simpler approach: instead of catching the actions you dispatch each time, monkeypatching the store or augmenting it, just assert on the output of your store, the state. That way you will be testing actions and reducers at the same time, and will get more confidence about how you store works with less code.
Sent with GitHawk
Depends on your OO design.
I encapsulate my state, actions, reducers, and middleware into one state module. The app imports the module and uses its public interface. It's not the app's concern to test the state module's internals. Its the state module's concern to ensure its API works as advertised.
If I did this more cleanly, I wouldn't pass my ReSwift.Store<AppState> object around directly in the app target, but write an adapter that I do control and assert that this is used appropriately.
Even then things can become a hassle.
Until 4 weeks ago, I had tests that used the main reducer, the action, and then asserted on the returned state. But I found I had to write a _ton_ of boilerplate for tests that target a specific case. Imagine a URL router, where some actions should simply be ignored when the current route is in the wrong state. Copy & paste for all affected actions. Then, a year later, change parts of the setup and adjust all the tests.
To make my tests less fragile for state changes like that (and me happier when I compile during refactorings), I redefined my _units_ of the unit tests and now call the action reducer with the action, the relevant _substate,_ then assert how it transforms the substate.
All in all, I don't assume it'll scale well to assert on the whole state change in your UI event handlers. That'd be way too many things mixed up for me.
Side question: Where does onActionDispatched() come from?
I added it on TestStore to emit item on ActionBuffer which is TestObserver. It subscribes TestStore.onActionDispatched() and caches actions. But it looks much better to implement to save actions in RecordingObject like yours. Thanks! @DivineDominion
I agree that verifying states is on the other concern. It could imply much things. State could be affected by reducer, action, and middleware, etc. I feel like testing the elements that affect the state separately would be much better.
Closing this because an answer seems to be given :)