【问题标题】:Unit Test for getting the PassthroughSubject publisher's value获取 PassthroughSubject 发布者值的单元测试
【发布时间】:2021-03-31 20:49:25
【问题描述】:

有没有办法在单元测试中获取 PassthroughSubject 发布者的价值? 我想测试一个函数是否返回成功并测试这个我想看看发布者值何时.loaded,然后是成功。

class HomeViewModel: ObservableObject {

    var homeState = PassthroughSubject<StatePublisher, Never>()

    func load(item: HomeModel) {
        self.homeState.send(.loading)
        self.dataSource.load(item: item) { result in
            switch result {
            case .success:
                self.homeState.send(.loaded)
            case let .failure(error):
                self.homeState.send(.error(message: error.localizedDescription))
            }
        }
    }

}
class HomeViewModelTests: XCTestCase {
    var sut: ViewModel!
    var subscriptions = Set<AnyCancellable>()
    
    override func setUpWithError() throws {
        sut = ViewModel()
    }

    override func tearDownWithError() throws {
        sut = nil
        subscriptions = []
    }
        
    func testUpdateHomeSuccess() {
        let expected = StatePublisher.loaded
        var result = StatePublisher.loading
    
        sut.load(item: HomeModel.fixture())
        
        sut.homeState
            .sink(receiveValue: { state in
                result = state
            })
            .store(in: &subscriptions)
    
        XCTAssert(
            result == expected,
            "Home expected to be \(expected) but was \(result)"
        )
    }
    
}

我尝试过这样的测试,但从未调用过 sink。

【问题讨论】:

  • Combine 框架是异步的。您必须使用异步测试!​​

标签: swift combine


【解决方案1】:

据推测,dataSource.load 是异步操作的。这意味着您需要在测试用例中使用XCTestExpectation。此外,由于您使用的是PassthroughSubject 而不是CurrentValueSubject,因此您应该开始加载之前创建订阅,以确保您不会错过任何已发布的值。 p>

let ex = XCTestExpectation()
sut.homeState
    .sink {
        if case .loaded = $0 {
            ex.fulfill()
        }
    }
    .store(in: &subscriptions)

wait(for: [ex], timeout: 10)
// Test fails if it doesn't call `ex.fulfill()` within 10 seconds.

阅读 Apple 的 Testing Asynchronous Operations with Expectations 指南。

【讨论】:

【解决方案2】:

我将PassthroughSubject 更改为CurrentValueSubject,因为在执行XCTestCase 时,该值的状态也存在。

所以这个测试总是失败:(PassthroughSubject 是在被测试的 viewModel 类中声明的)

var someValue: PassthroughSubject<Bool, Never> = .init()
func someTest() throws {
    let exp = expectation(description: "Value changed")
    
    viewModel.functionChangingTheValue()

    viewModel.someValue.sink { result in
        XCTAssertTrue(result)
        exp.fulfill()
    }
    .store(in: &cancellables)

    wait(for: [exp], timeout: 0.1)
}

但这一次成功了

var someValue: CurrentValueSubject<Bool, Never> = .init(false)
func someTest() throws {
    let exp = expectation(description: "Value changed")
    
    viewModel.functionChangingTheValue()

    viewModel.someValue.sink { result in
        XCTAssertTrue(result)
        exp.fulfill()
    }
    .store(in: &cancellables)

    wait(for: [exp], timeout: 0.1)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多