I'm running into an issue with snapshot testing a view that listens for updates from a view model using a Combine publisher. The issue seems to be with the sink that is received on the main queue - the closure is what updates the view state. When I attach the view model to the view, the closure inside the sink is not executed until after the snapshot test runs, and I'm left with an empty button! Is there a way to force the sink's closure to execute before running the test?
import Combine
import SnapshotTesting
import UIKit
import XCTest
struct ButtonState {
let text: String
let color: UIColor
}
protocol ButtonViewModel {
var state: AnyPublisher<ButtonState, Never> { get }
}
final class MyButton: UIButton {
private var cancellable: Cancellable?
func attach(viewModel: some ButtonViewModel) {
cancellable = viewModel.state
.receive(on: DispatchQueue.main)
.sink { [weak self] in
self?.convertState($0)
}
}
private func convertState(_ state: ButtonState) {
setTitle(state.text, for: .normal)
setTitleColor(state.color, for: .normal)
}
}
struct MockViewModel: ButtonViewModel {
var state: AnyPublisher<ButtonState, Never> {
currentState.eraseToAnyPublisher()
}
private let currentState: CurrentValueSubject<ButtonState, Never>
init(initialState: ButtonState) {
currentState = CurrentValueSubject(initialState)
}
}
final class ButtonSnapshotTest: XCTestCase {
func testMyButton() {
let myButton = MyButton()
myButton.attach(viewModel: MockViewModel(initialState: .init(text: "Hi", color: .blue)))
assertSnapshot(matching: myButton, as: .image)
}
}