So I have this problem with a @Published
realm result that is not updating in tests.
Basically what I am trying to do is, to test if a realm update is causing my ViewModel to update the ui. To do this I am subscribing to the published field.
This is the Test:
func testListUpdate() {
// when
let expectation = XCTestExpectation(description: "Wasn't updated.")
_ = sut.$stocks
.dropFirst()
.sink { value in
expectation.fulfill()
}
try! realm.write {
realm.add(Stock(value: ["name": "Test3"]))
}
// then
wait(for: [expectation], timeout: 1)
}
sut
is the ViewModel which looks like this:
import Foundation
import RealmSwift
class StockListViewModel: ViewModelBase, ObservableObject {
let stockRepository: StockRepository
@Published var stocks: Results<Stock>
init(stockRepository: StockRepository) {
self.stockRepository = stockRepository
stocks = stockRepository.all
.sorted(byKeyPath: "createdAt", ascending: true)
super.init(title: "StockList_title".localize())
self.notificationTokens.append(
stocks
.observe({ change in
switch change {
case .update(let updated, _, _, _):
self.stocks = updated
default: break
}
})
)
}
}
As you can see, the viewmodel has as `@Published var stocks: Results.
In the initializer the stocks are set (this update is emitted by the Publisher
successfully and tested in another case.
Then I assign a realm change listener to those stocks and when it updates, I set the stocks new (see in the observe
closure). This update is really called, but the sink
in the test never emits the update. Thus the test fails.
So my question is: Why is the Publisher
emitting the initial value, but never the updated one?
CodePudding user response:
When working with Combine, you always need to keep a reference to the AnyCancellable
representing your subscription - in your test case, returned by the sink
call, since the subscription chain is only kept alive while the AnyCancellable
is kept in memory. Publisher
s only emit values when they have subscribers - so if you throw away the subscription, your $stocks
Publisher
will have no subscribers and hence it won't emit any values.
You are throwing away the subscription by throwing away the return value from sink
.
_ = sut.$stocks
.dropFirst()
.sink
Instead, you need to store the return value on your test class as an instance property.
final class YourTestClass: XCTestCase {
var cancellable: AnyCancellable?
func testListUpdate() {
// when
let expectation = XCTestExpectation(description: "Wasn't updated.")
cancellable = sut.$stocks
.dropFirst()
.sink { value in
expectation.fulfill()
}
try! realm.write {
realm.add(Stock(value: ["name": "Test3"]))
}
// then
wait(for: [expectation], timeout: 1)
}
}
CodePudding user response:
The problem was that the subscription of the Publisher
was not saved.
So the line
_ = sut.$stocks
should be
cancellable = sut.$stocks
where cancellable
is a field of the testclass.