Home > front end >  @Published not emitting update in tests
@Published not emitting update in tests

Time:04-05

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. Publishers 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.

  • Related