Look at the following demo code:
import ComposableArchitecture
struct DemoFeature: ReducerProtocol {
struct State: Equatable {
@BindableState var count: Int = 0 {
willSet {
print("Changing \(lastCount) to \(count)")
lastCount = count
}
}
var lastCount: Int = 0
}
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
}
var body: some ReducerProtocol<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding(\.$count):
print("Reducer changing \(state.lastCount) to \(state.count)")
return .none
case .binding:
return .none
}
}
}
}
And the following test cases:
func testCountDirectly() throws {
var state = DemoFeature.State()
XCTAssertEqual(state.count, 0)
XCTAssertEqual(state.lastCount, 0)
state.count = 10
XCTAssertEqual(state.count, 10)
XCTAssertEqual(state.lastCount, 0)
state.count = 20
XCTAssertEqual(state.count, 20)
XCTAssertEqual(state.lastCount, 10)
}
This passes with the output:
Changing 0 to 0
Changing 0 to 10
Now I try to mutate the value through the store:
func testCount() throws {
let store = TestStore(
initialState: .init(),
reducer: DemoFeature()
)
XCTAssertEqual(store.state.count, 0)
XCTAssertEqual(store.state.lastCount, 0)
_ = store.send(.set(\.$count, 10)) {
$0.count = 10
}
_ = store.send(.set(\.$count, 20)) {
$0.count = 20
$0.lastCount = 10
}
}
Which fails:
Reducer changing 0 to 10
Changing 0 to 0
Reducer changing 0 to 20
Changing 0 to 10
Test_SetDemo.swift:39: error: -[TestDemoFeature testCount] : A state change does not match expectation: …
DemoFeature.State(
_count: 20,
− lastCount: 10
+ lastCount: 0
)
(Expected: −, Actual: +)
Even weirder: I can remove $0.lastCount = 10
in the last test and I will still get the same output. So even if I don't tell the test, that I'm expecting lastCount
to be 10
it would still fail, telling me it should be 10
.
Does anyone know what's going on here?
(I know I can probably use onChange
in a view to track changes to a binding, but in my real world use case I fear it would not track changes happening during setup outside of the view. And testing without the view would be hard.)