单元测试 SwiftUI/Combine @Published 布尔值
Unit testing SwiftUI/Combine @Published boolean values
我正在尝试熟悉在 SwiftUI 中对某些视图模型进行单元测试。视图模型当前有两个 @Published
布尔值,它们在基础 UserDefaults
属性 更改时发布更改。对于我的单元测试,我遵循 this guide 如何设置 UserDefaults
进行测试,这样我的生产值就不会被修改。我可以这样测试默认值:
func testDefaultValue() {
XCTAssertFalse(viewModel.canDoThing)
}
我将如何切换 @Published
值然后确保我的视图模型已收到更改?因此,例如,我在我的 XCTestCase 中引用了我的模拟用户默认值。我试图以零成功执行以下操作:
func testValueTogglesToTrue() {
defaults.canDoThing = true
XCTAssertTrue(viewModel.canDoThing)
}
我们的想法是更新底层用户默认值,即发布对视图模型中已发布值的更改将通知我们的视图模型。以上对视图模型变量没有任何作用。我是否需要订阅发布者并使用接收器来完成此操作?
假设您在 UserDefaults
中存储了一个标志以了解用户是否已完成入职:
extension UserDefaults {
@objc dynamic public var completedOnboarding: Bool {
bool(forKey: "completedOnboarding")
}
}
您有一个 ViewModel,它告诉您 View
是否显示入职,并且有一个方法将入职标记为已完成:
class ViewModel: ObservableObject {
@Published private(set) var showOnboarding: Bool = true
private let userDefaults: UserDefaults
public init(userDefaults: UserDefaults) {
self.userDefaults = userDefaults
self.showOnboarding = !userDefaults.completedOnboarding
userDefaults
.publisher(for: \.completedOnboarding)
.map { ![=11=] }
.receive(on: RunLoop.main)
.assign(to: &$showOnboarding)
}
public func completedOnboarding() {
userDefaults.set(true, forKey: "completedOnboarding")
}
}
为了测试这个 class 你有一个 XCTestCase
:
class MyTestCase: XCTestCase {
private var userDefaults: UserDefaults!
private var cancellables = Set<AnyCancellable>()
override func setUpWithError() throws {
try super.setUpWithError()
userDefaults = try XCTUnwrap(UserDefaults(suiteName: #file))
userDefaults.removePersistentDomain(forName: #file)
}
// ...
}
一些测试用例是同步的,例如您可以轻松测试 showOnboarding 依赖于 UserDefaults
completedOnboarding 属性:
func test_whenCompletedOnboardingFalse_thenShowOnboardingTrue() {
userDefaults.set(false, forKey: "completedOnboarding")
let subject = ViewModel(userDefaults: userDefaults)
XCTAssert(subject.showOnboarding)
}
func test_whenCompletedOnboardingTrue_thenShowOnboardingFalse() {
userDefaults.set(true, forKey: "completedOnboarding")
let subject = ViewModel(userDefaults: userDefaults)
XCTAssertFalse(subject.showOnboarding)
}
有些测试是异步的,这意味着您需要使用 XCTExpectation
s 等待 @Published
值更改:
func test_whenCompleteOnboardingCalled_thenShowOnboardingFalse() {
let subject = ViewModel(userDefaults: userDefaults)
// first define the expectation that showOnboarding will change to false (1)
let showOnboardingFalse = expectation(
description: "when completedOnboarding called then show onboarding is false")
// subscribe to showOnboarding publisher to know when the value changes (2)
subject
.$showOnboarding
.filter { ![=14=] }
.sink { _ in
// when false received fulfill the expectation (5)
showOnboardingFalse.fulfill()
}
.store(in: &cancellables)
// trigger the function that changes the value (3)
subject.completedOnboarding()
// tell the tests to wait for your expectation (4)
waitForExpectations(timeout: 0.1)
}
我正在尝试熟悉在 SwiftUI 中对某些视图模型进行单元测试。视图模型当前有两个 @Published
布尔值,它们在基础 UserDefaults
属性 更改时发布更改。对于我的单元测试,我遵循 this guide 如何设置 UserDefaults
进行测试,这样我的生产值就不会被修改。我可以这样测试默认值:
func testDefaultValue() {
XCTAssertFalse(viewModel.canDoThing)
}
我将如何切换 @Published
值然后确保我的视图模型已收到更改?因此,例如,我在我的 XCTestCase 中引用了我的模拟用户默认值。我试图以零成功执行以下操作:
func testValueTogglesToTrue() {
defaults.canDoThing = true
XCTAssertTrue(viewModel.canDoThing)
}
我们的想法是更新底层用户默认值,即发布对视图模型中已发布值的更改将通知我们的视图模型。以上对视图模型变量没有任何作用。我是否需要订阅发布者并使用接收器来完成此操作?
假设您在 UserDefaults
中存储了一个标志以了解用户是否已完成入职:
extension UserDefaults {
@objc dynamic public var completedOnboarding: Bool {
bool(forKey: "completedOnboarding")
}
}
您有一个 ViewModel,它告诉您 View
是否显示入职,并且有一个方法将入职标记为已完成:
class ViewModel: ObservableObject {
@Published private(set) var showOnboarding: Bool = true
private let userDefaults: UserDefaults
public init(userDefaults: UserDefaults) {
self.userDefaults = userDefaults
self.showOnboarding = !userDefaults.completedOnboarding
userDefaults
.publisher(for: \.completedOnboarding)
.map { ![=11=] }
.receive(on: RunLoop.main)
.assign(to: &$showOnboarding)
}
public func completedOnboarding() {
userDefaults.set(true, forKey: "completedOnboarding")
}
}
为了测试这个 class 你有一个 XCTestCase
:
class MyTestCase: XCTestCase {
private var userDefaults: UserDefaults!
private var cancellables = Set<AnyCancellable>()
override func setUpWithError() throws {
try super.setUpWithError()
userDefaults = try XCTUnwrap(UserDefaults(suiteName: #file))
userDefaults.removePersistentDomain(forName: #file)
}
// ...
}
一些测试用例是同步的,例如您可以轻松测试 showOnboarding 依赖于 UserDefaults
completedOnboarding 属性:
func test_whenCompletedOnboardingFalse_thenShowOnboardingTrue() {
userDefaults.set(false, forKey: "completedOnboarding")
let subject = ViewModel(userDefaults: userDefaults)
XCTAssert(subject.showOnboarding)
}
func test_whenCompletedOnboardingTrue_thenShowOnboardingFalse() {
userDefaults.set(true, forKey: "completedOnboarding")
let subject = ViewModel(userDefaults: userDefaults)
XCTAssertFalse(subject.showOnboarding)
}
有些测试是异步的,这意味着您需要使用 XCTExpectation
s 等待 @Published
值更改:
func test_whenCompleteOnboardingCalled_thenShowOnboardingFalse() {
let subject = ViewModel(userDefaults: userDefaults)
// first define the expectation that showOnboarding will change to false (1)
let showOnboardingFalse = expectation(
description: "when completedOnboarding called then show onboarding is false")
// subscribe to showOnboarding publisher to know when the value changes (2)
subject
.$showOnboarding
.filter { ![=14=] }
.sink { _ in
// when false received fulfill the expectation (5)
showOnboardingFalse.fulfill()
}
.store(in: &cancellables)
// trigger the function that changes the value (3)
subject.completedOnboarding()
// tell the tests to wait for your expectation (4)
waitForExpectations(timeout: 0.1)
}