我正在努力进行Published对象的单元测试。
我有一个视图模型类,如下所示
class MovieListViewModel {
@Published public private(set) var arrayOfMovies: [Movie] = []
@Published private var arraofFavoriteMoviesID: [FavouriteMovieID] = []
init(request: NetworkServiceProtocol) {
addSubscribers()
callServicesInSequence()
}
func addSubscribers() {
$arrayOfMovies.combineLatest($arraofFavoriteMoviesID)
.debounce(for: 0.0, scheduler: DispatchQueue.main)
.sink { [weak self] (_, _) in
self?.fetchWachedMovies()
self?.fetchTobeWatchedMovies()
self?.fetchFavoriteMovies()
}
.store(in: &subscriptions)
}
func callServicesInSequence() {/*..service request...*}
}
在这里addSubscribers()监听arrayOfMovies
或arraofFavoriteMoviesID
中发生的任何更改,并在应用程序中完美运行。
但是当我试着模拟和编写单元测试用例时,在arrayOfMovies
或arraofFavoriteMoviesID
中发生的任何更改都不会产生任何影响(addSubscribers的主体永远不会被调用)。
在为合并/Published对象编写单元测试用例时,有谁能告诉我我做错了什么吗?
如需进一步澄清,请告知。
1条答案
按热度按时间t3irkdon1#
您的代码有两个明显的依赖项:一个
NetworkServiceProtocol
和一个DispatchQueue.main
。NetworkServiceProtocol
不是iOS SDK的一部分,我假设它是您创建的一个类型,并且您将它传递给模型的init
,这样您就可以在测试用例中替换一个可测试的实现。但是,
DispatchQueue
是iOS SDK的一部分,您不能创建自己的可测试实现以用于测试用例,您运行主队列的能力有限,这使得测试依赖于它的代码变得困难。以下是三种解决方案:
DispatchQueue.main
,并将其传递给模型的init
。然后,在测试中,您可以传递在测试用例中控制的确定性调度器。例如,Combine Schedulers包提供了类型擦除器AnyScheduler
和几个调度器实现,专门用于测试。(上面提到的TCA使用此软件包。)为
Scheduler
协议编写自己的type eraser非常简单,如果不想依赖第三方软件包,可以自己编写。XCTestExpectation
API在测试用例中运行主队列。您没有发布足够的代码来进行演示,因此我将使用以下简单类型:
下面是使用它们的简化模型:
为了进行测试,我可以设置一个
NetworkClient
,其中fetchMovies
是一个PassthroughSubject
,这样我的测试用例就可以准确地决定“网络”发送什么以及何时发送。为了测试成功案例,即网络“工作”的情况,我订阅了模型的
$movies
发布器,如果它发布了正确的值,则实现一个XCTestExpectation
。为了测试网络“失败”的失败案例,我订阅了
$error
发布器,如果它发布了正确的错误代码,则实现XCTestExpectation
。但请注意,如果测试失败(例如,如果您更改
testFailure
以故意发布错误的代码),失败需要2秒。这很烦人。这两个测试足够简单,我们可以重写它们,以便在发布错误内容的情况下更快地失败。但一般来说,当依赖XCTestExpectation
时,可能很难将所有测试用例编写为“快速失败”。这类问题可以通过用类型擦除器代替直接使用DispatchQueue
来避免,它允许您的测试用例使用一个可控的调度器,这样测试用例可以使时间立即流动,而不需要使用DispatchQueue
,因此您根本不需要使用XCTestExpectation
。