Last active
April 8, 2021 17:49
-
-
Save bocato/bfe41dd4f8ac39f29ec96683274b8a56 to your computer and use it in GitHub Desktop.
mvvm_form_4.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
import SwiftUI | |
import XCTest | |
final class StateBindingViewModelTests: XCTestCase { | |
// MARK: - Tests | |
func test_binding_whenSetterIsCalled_shouldUpdateStateValues() { | |
// Given | |
let sut: TestViewModelMock<TestState> = .init( | |
initialState: .init( | |
textValue: "", | |
intValue: 0 | |
) | |
) | |
let textBinding: Binding<String> = sut.binding(\.textValue) | |
let newValue = "New Value" | |
// When | |
textBinding.wrappedValue = newValue | |
// Then | |
XCTAssertEqual(sut.state.textValue, newValue) | |
} | |
func test_binding_whenGetterIsCalled_shouldReturnStateValues() { | |
// Given | |
let sut: TestViewModelMock<TestState> = .init( | |
initialState: .init( | |
textValue: "", | |
intValue: 123 | |
) | |
) | |
// When | |
let intBinding: Binding<Int> = sut.binding(\.intValue) | |
// Then | |
XCTAssertEqual(sut.state.intValue, intBinding.wrappedValue) | |
} | |
func test_update_shouldSetTheNewValueOnState() { | |
// Given | |
let sut: TestViewModelMock<TestState> = .init( | |
initialState: .init( | |
textValue: "", | |
intValue: 123 | |
) | |
) | |
// When | |
sut.update(\.textValue, to: "New Value") | |
// Then | |
XCTAssertEqual(sut.state.textValue, "New Value") | |
} | |
func test_stateWillChange_whenNotOverriden_shouldAlwaysReturnTrue() { | |
// Given | |
let initialValue = "Initial Value" | |
let sut: NonBlockingTestViewModelMock = .init( | |
initialState: .init( | |
textValue: initialValue, | |
intValue: 0 | |
) | |
) | |
let textBinding: Binding<String> = sut.binding(\.textValue) | |
let newValue = "New Value" | |
// When | |
textBinding.wrappedValue = newValue | |
// Then | |
XCTAssertTrue(sut.onStateChangeCalled) | |
XCTAssertEqual(sut.onStateChangeKeyPathsPassed.last, \TestState.textValue) | |
XCTAssertEqual(sut.state.textValue, newValue) | |
} | |
func test_stateWillChange_shouldAlwaysBeCalled() { | |
// Given | |
let sut: TestViewModelMock<TestState> = .init( | |
initialState: .init( | |
textValue: "", | |
intValue: 0 | |
) | |
) | |
let textBinding: Binding<String> = sut.binding(\.textValue) | |
let newValue = "New Value" | |
// When | |
textBinding.wrappedValue = newValue | |
// Then | |
XCTAssertTrue(sut.stateWillChangeValueCalled) | |
XCTAssertEqual(sut.stateWillChangeValueKeyPathsPassed.last, \TestState.textValue) | |
XCTAssertEqual(sut.stateWillChangeValueNewValuesPassed.last as? String, newValue) | |
} | |
func test_onStateChange_whenStateWillChangeIsFalse_itShouldNotBeCalled_andTheStateShouldNotBeUpdated() { | |
// Given | |
let initialValue = "Initial Value" | |
let sut: TestViewModelMock<TestState> = .init( | |
initialState: .init( | |
textValue: initialValue, | |
intValue: 0 | |
) | |
) | |
let newValue = "New Value" | |
sut.stateWillChangeValueBooleanToBeReturned = false | |
let textBinding: Binding<String> = sut.binding(\.textValue) | |
// When | |
textBinding.wrappedValue = newValue | |
// Then | |
XCTAssertTrue(sut.stateWillChangeValueCalled) | |
XCTAssertEqual(sut.stateWillChangeValueNewValuesPassed.last as? String, newValue) | |
XCTAssertFalse(sut.onStateChangeCalled, "`onStateChange` should not be called.") | |
XCTAssertNotEqual(sut.state.textValue, newValue, "The `textValue` should not change to the `newValue`.") | |
XCTAssertEqual(sut.state.textValue, initialValue, "The `textValue` should still be the `initialValue`.") | |
} | |
func test_onStateChange_whenStateWillChangeIsTrue_itShouldBeCalled_andTheStateShouldBeUpdated() { | |
let initialValue = 1 | |
let sut: TestViewModelMock<TestState> = .init( | |
initialState: .init( | |
textValue: "", | |
intValue: initialValue | |
) | |
) | |
sut.stateWillChangeValueBooleanToBeReturned = true | |
let intBinding: Binding<Int> = sut.binding(\.intValue) | |
let newValue = 2 | |
// When | |
intBinding.wrappedValue = newValue | |
// Then | |
XCTAssertTrue(sut.onStateChangeCalled) | |
XCTAssertEqual(sut.onStateChangeKeyPathsPassed.last, \TestState.intValue) | |
XCTAssertEqual(sut.state.intValue, newValue) | |
} | |
} | |
// MARK: - Test Doubles and Helpers | |
struct TestState: Equatable { | |
var textValue: String | |
var intValue: Int | |
} | |
final class TestViewModelMock<StateType: Equatable>: StateBindingViewModel<StateType> { | |
private(set) var stateWillChangeValueCallCount = 0 | |
var stateWillChangeValueCalled: Bool { stateWillChangeValueCallCount > 0 } | |
private(set) var stateWillChangeValueKeyPathsPassed: [PartialKeyPath<StateType>] = [] | |
private(set) var stateWillChangeValueNewValuesPassed: [Any] = [] | |
var stateWillChangeValueBooleansToBeReturned: [Bool] = [] | |
var stateWillChangeValueBooleanToBeReturned = true | |
override func stateWillChangeValue<Value>( | |
_ keyPath: PartialKeyPath<StateType>, | |
newValue: Value | |
) -> Bool where Value: Equatable { | |
stateWillChangeValueCallCount += 1 | |
stateWillChangeValueKeyPathsPassed.append(keyPath) | |
stateWillChangeValueNewValuesPassed.append(newValue) | |
return stateWillChangeValueBooleansToBeReturned.isEmpty ? | |
stateWillChangeValueBooleanToBeReturned : | |
stateWillChangeValueBooleansToBeReturned[stateWillChangeValueCallCount] | |
} | |
private(set) var onStateChangeCallCount = 0 | |
var onStateChangeCalled: Bool { onStateChangeCallCount > 0 } | |
private(set) var onStateChangeKeyPathsPassed: [PartialKeyPath<StateType>] = [] | |
override func onStateChange(_ keyPath: PartialKeyPath<StateType>) { | |
onStateChangeCallCount += 1 | |
onStateChangeKeyPathsPassed.append(keyPath) | |
} | |
} | |
final class NonBlockingTestViewModelMock: StateBindingViewModel<TestState> { | |
private(set) var onStateChangeCallCount = 0 | |
var onStateChangeCalled: Bool { onStateChangeCallCount > 0 } | |
private(set) var onStateChangeKeyPathsPassed: [PartialKeyPath<TestState>] = [] | |
override func onStateChange(_ keyPath: PartialKeyPath<TestState>) { | |
onStateChangeCallCount += 1 | |
onStateChangeKeyPathsPassed.append(keyPath) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment