mackoj
(Jeffrey Macko)
1
Hi,
Is there a way to do SnapshotTesting by using the .do function when asserting on the TestStore in between .send and .receive ?
testStore.assert([
.do { assertSnapshot(matching: view, as: .windowedImage, named: "initialState") },
.send(.mapContainer(.mapView(.locationManager(.didUpdateLocations([parisLocation]))))) {
$0.mapContainer.mapView.userLocationCoordinate = parisLocation
},
.receive(.mapContainer(.mapView(.centerOnUserReceived))) {
$0.mapContainer.mapView.mapCenter = parisLocation.coordinate
},
.do { assertSnapshot(matching: view, as: .windowedImage, named: "afterDidUpdateLocations(\(parisLocation)") },
.send(.mapContainer(.floatingButtonView(.searchAgainTapped))),
.receive(.refreshItemsFromMapCoordsReceived),
.receive(.overlayContainer(.overlayLocal(.updateLevelReceived(.minimum)))),
.receive(.mapContainer(.floatingButtonView(.hideSearchAgainReceived(true)))),
.receive(.overlayContainer(.overlayLocal(.refreshPositionReceived))) {
$0.overlayContainer.overlayLocal.position.height = -60
},
.do { assertSnapshot(matching: view, as: .windowedImage, named: "afterSearchAgainTapped") },
.send(.mapContainer(.mapView(.userTappedPOI(selectedPro)))) {
$0.selectedPro = selectedPro
$0.content = .fd
},
.receive(.overlayContainer(.overlayLocal(.updateLevelReceived(.half)))) {
$0.overlayContainer.overlayLocal.level = .half
},
.receive(.mapContainer(.mapView(.centerOnSelectedProReceived))) {
$0.mapContainer.mapView.mapCenter = selectedPro?.coordinates
},
.receive(.overlayContainer(.overlayLocal(.refreshPositionReceived))) {
$0.overlayContainer.overlayLocal.position.height = 0
},
.do { assertSnapshot(matching: view, as: .windowedImage, named: "afterUserTappedPOI\(selectedPro)") },
])
For this to work we need TestStore to provide a way to return a proper Store and feed TestStore to the View.
1 Like
ldstreet
(Luke Street)
2
I would love to see something like this baked right in. But for now, how about an extension like this?
import SwiftUI
import ComposableArchitecture
import SnapshotTesting
extension TestStore.Step {
static func receive<V: View>(
_ action: Action,
view: @escaping (Store<LocalState, LocalAction>) -> V,
_ update: @escaping(inout LocalState) -> Void
) -> Self {
.receive(action, { state in
let store = Store<LocalState, LocalAction>.init(initialState: state, reducer: .empty, environment: ())
let view = view(store)
assertSnapshot(matching: view, as: .image(layout: .device(config: .iPhone8Plus)))
update(&state)
})
}
static func send<V: View>(
_ action: LocalAction,
view: @escaping (Store<LocalState, LocalAction>) -> V,
_ update: @escaping(inout LocalState) -> Void
) -> Self {
.send(action, { state in
let store = Store<LocalState, LocalAction>.init(initialState: state, reducer: .empty, environment: ())
let view = view(store)
assertSnapshot(matching: view, as: .image(layout: .device(config: .iPhone8Plus)))
update(&state)
})
}
}
I would love to see if we can improve ergonomics on this function even more. An obvious start would be to parameterize the Snapshotting strategy.
3 Likes
Hey Luke, this solution works great. I think I've made a few improvements:
- Passing in parameters for
#line, #function, and #file improves the test output in Xcode and helps organize the snapshots
- Calling
update(&state) first ensures that the state change is applied before the snapshot is taken.
- Calling
assertSnapshot in a DispatchQueue.main.async block seems to speed up the tests by an order of magnitude (100-300ms down to 10-20ms). I think this is because instead of blocking at each step, the snapshots are deferred until the next iteration of the run loop and executed as a batch.
remlostime
(Kai Chen)
4
Hi, just want to bump this topic. Do we have any update on this?
Hi @remlostime! Is there anything you're specifically asking for an update of? There seem to be some suggested solutions to the problem earlier in the thread, though they may need to be updated for the TestStore's latest API.
remlostime
(Kai Chen)
6
@stephencelis Thanks for the reply. I double checked the comments, and find the solution is a little bit out of date. I refactored it to fit the latest TCA and fix some bugs.
extension TestStore where Action: Equatable, LocalState: Equatable {
func receive<V: View>(
_ action: Action,
view: @escaping (Store<LocalState, LocalAction>) -> V,
update: @escaping(inout LocalState) -> Void
) {
receive(action, { state in
update(&state)
let store = Store<LocalState, LocalAction>.init(initialState: state, reducer: .empty, environment: ())
let view = view(store)
assertSnapshot(matching: view, as: .image(layout: .device(config: .iPhone8Plus)))
})
}
func send<V: View>(
_ action: LocalAction,
view: @escaping (Store<LocalState, LocalAction>) -> V,
update: @escaping(inout LocalState) -> Void
) {
send(action, { state in
update(&state)
let store = Store<LocalState, LocalAction>.init(initialState: state, reducer: .empty, environment: ())
let view = view(store)
assertSnapshot(matching: view, as: .image(layout: .device(config: .iPhone8Plus)))
})
}
}