Swift Testing for Unit Tests PART 1 (#5119)
* migrated a lot of unit tests to Swift Testing and added a new implementation for deferred fulfillment more tests migration Cleaned the code manually to establish some good patterns more code improvements some more code improvements removed empty tests update project * more pr suggestions and cleanups * removed the TestSetup pattern * fixing claude not reusing tests * pr suggestion + added indent rule to swiftformat so that we can prevent AIs to change that
This commit is contained in:
@@ -7,165 +7,186 @@
|
||||
//
|
||||
|
||||
@testable import ElementX
|
||||
import XCTest
|
||||
import Testing
|
||||
|
||||
@MainActor
|
||||
class PollFormScreenViewModelTests: XCTestCase {
|
||||
let timelineProxy = TimelineProxyMock(.init())
|
||||
@Suite
|
||||
struct PollFormScreenViewModelTests {
|
||||
private let timelineProxy = TimelineProxyMock(.init())
|
||||
|
||||
var viewModel: PollFormScreenViewModelProtocol!
|
||||
var context: PollFormScreenViewModelType.Context {
|
||||
private var viewModel: PollFormScreenViewModelProtocol!
|
||||
private var context: PollFormScreenViewModelType.Context {
|
||||
viewModel.context
|
||||
}
|
||||
|
||||
func testNewPollInitialState() async throws {
|
||||
@Test
|
||||
mutating func newPollInitialState() async throws {
|
||||
setupViewModel()
|
||||
|
||||
XCTAssertEqual(context.options.count, 2)
|
||||
XCTAssertTrue(context.options.allSatisfy(\.text.isEmpty))
|
||||
XCTAssertTrue(context.question.isEmpty)
|
||||
XCTAssertTrue(context.viewState.isSubmitButtonDisabled)
|
||||
XCTAssertFalse(context.viewState.bindings.isUndisclosed)
|
||||
#expect(context.options.count == 2)
|
||||
// This due to a bug in Swift testing that raises an error when allSatisfy is used in an #expect
|
||||
let isEmpty = context.options.allSatisfy(\.text.isEmpty)
|
||||
#expect(isEmpty)
|
||||
#expect(context.question.isEmpty)
|
||||
#expect(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(!context.viewState.bindings.isUndisclosed)
|
||||
|
||||
// Cancellation should work without confirmation
|
||||
let deferred = deferFulfillment(viewModel.actions) { _ in true }
|
||||
context.send(viewAction: .cancel)
|
||||
let action = try await deferred.fulfill()
|
||||
XCTAssertNil(context.alertInfo)
|
||||
XCTAssertEqual(action, .close)
|
||||
#expect(context.alertInfo == nil)
|
||||
#expect(action == .close)
|
||||
}
|
||||
|
||||
func testEditPollInitialState() async throws {
|
||||
@Test
|
||||
mutating func editPollInitialState() async throws {
|
||||
setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed))
|
||||
|
||||
XCTAssertEqual(context.options.count, 3)
|
||||
XCTAssertTrue(context.options.allSatisfy { !$0.text.isEmpty })
|
||||
XCTAssertFalse(context.question.isEmpty)
|
||||
XCTAssertTrue(context.viewState.isSubmitButtonDisabled)
|
||||
XCTAssertFalse(context.viewState.bindings.isUndisclosed)
|
||||
#expect(context.options.count == 3)
|
||||
#expect(context.options.allSatisfy { !$0.text.isEmpty })
|
||||
#expect(!context.question.isEmpty)
|
||||
#expect(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(!context.viewState.bindings.isUndisclosed)
|
||||
|
||||
// Cancellation should work without confirmation
|
||||
let deferred = deferFulfillment(viewModel.actions) { _ in true }
|
||||
context.send(viewAction: .cancel)
|
||||
let action = try await deferred.fulfill()
|
||||
XCTAssertNil(context.alertInfo)
|
||||
XCTAssertEqual(action, .close)
|
||||
#expect(context.alertInfo == nil)
|
||||
#expect(action == .close)
|
||||
}
|
||||
|
||||
func testNewPollInvalidEmptyOption() {
|
||||
@Test
|
||||
mutating func newPollInvalidEmptyOption() {
|
||||
setupViewModel()
|
||||
|
||||
context.question = "foo"
|
||||
context.options[0].text = "bla"
|
||||
context.options[1].text = "bla"
|
||||
context.send(viewAction: .addOption)
|
||||
XCTAssertTrue(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(context.viewState.isSubmitButtonDisabled)
|
||||
}
|
||||
|
||||
func testEditPollInvalidEmptyOption() {
|
||||
@Test
|
||||
mutating func editPollInvalidEmptyOption() {
|
||||
setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed))
|
||||
|
||||
context.send(viewAction: .addOption)
|
||||
XCTAssertTrue(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(context.viewState.isSubmitButtonDisabled)
|
||||
|
||||
// Cancellation requires a confirmation
|
||||
context.send(viewAction: .cancel)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
#expect(context.alertInfo != nil)
|
||||
}
|
||||
|
||||
func testEditPollSubmitButtonState() {
|
||||
@Test
|
||||
mutating func editPollSubmitButtonState() {
|
||||
setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed))
|
||||
|
||||
XCTAssertTrue(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(context.viewState.isSubmitButtonDisabled)
|
||||
context.options[0].text = "foo"
|
||||
XCTAssertFalse(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(!context.viewState.isSubmitButtonDisabled)
|
||||
|
||||
// Cancellation requires a confirmation
|
||||
context.send(viewAction: .cancel)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
#expect(context.alertInfo != nil)
|
||||
}
|
||||
|
||||
func testNewPollSubmit() async throws {
|
||||
@Test
|
||||
mutating func newPollSubmit() async throws {
|
||||
setupViewModel()
|
||||
|
||||
context.question = "foo"
|
||||
context.options[0].text = "bla1"
|
||||
context.options[1].text = "bla2"
|
||||
XCTAssertFalse(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(!context.viewState.isSubmitButtonDisabled)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .close }
|
||||
let expectation = XCTestExpectation(description: "Create poll")
|
||||
timelineProxy.createPollQuestionAnswersPollKindClosure = { question, options, kind in
|
||||
XCTAssertEqual(question, "foo")
|
||||
XCTAssertEqual(options.count, 2)
|
||||
XCTAssertEqual(options[0], "bla1")
|
||||
XCTAssertEqual(options[1], "bla2")
|
||||
XCTAssertEqual(kind, .disclosed)
|
||||
expectation.fulfill()
|
||||
return .success(())
|
||||
}
|
||||
context.send(viewAction: .submit)
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 1)
|
||||
try await deferred.fulfill()
|
||||
try await confirmation { confirmation in
|
||||
timelineProxy.createPollQuestionAnswersPollKindClosure = { question, options, kind in
|
||||
#expect(question == "foo")
|
||||
#expect(options.count == 2)
|
||||
#expect(options[0] == "bla1")
|
||||
#expect(options[1] == "bla2")
|
||||
#expect(kind == .disclosed)
|
||||
confirmation()
|
||||
return .success(())
|
||||
}
|
||||
context.send(viewAction: .submit)
|
||||
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
func testEditPollSubmit() async throws {
|
||||
@Test
|
||||
mutating func editPollSubmit() async throws {
|
||||
setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed))
|
||||
|
||||
context.question = "What is your favorite country?"
|
||||
context.options.append(.init(text: "France 🇫🇷"))
|
||||
XCTAssertFalse(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(!context.viewState.isSubmitButtonDisabled)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .close }
|
||||
let expectation = XCTestExpectation(description: "Edit poll")
|
||||
timelineProxy.editPollOriginalQuestionAnswersPollKindClosure = { eventID, question, options, kind in
|
||||
XCTAssertEqual(eventID, "foo")
|
||||
XCTAssertEqual(question, "What is your favorite country?")
|
||||
XCTAssertEqual(options.count, 4)
|
||||
XCTAssertEqual(options[0], "Italy 🇮🇹")
|
||||
XCTAssertEqual(options[1], "China 🇨🇳")
|
||||
XCTAssertEqual(options[2], "USA 🇺🇸")
|
||||
XCTAssertEqual(options[3], "France 🇫🇷")
|
||||
XCTAssertEqual(kind, .disclosed)
|
||||
expectation.fulfill()
|
||||
return .success(())
|
||||
}
|
||||
context.send(viewAction: .submit)
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 1)
|
||||
try await deferred.fulfill()
|
||||
try await confirmation { confirmation in
|
||||
timelineProxy.editPollOriginalQuestionAnswersPollKindClosure = { eventID, question, options, kind in
|
||||
#expect(eventID == "foo")
|
||||
#expect(question == "What is your favorite country?")
|
||||
#expect(options.count == 4)
|
||||
#expect(options[0] == "Italy 🇮🇹")
|
||||
#expect(options[1] == "China 🇨🇳")
|
||||
#expect(options[2] == "USA 🇺🇸")
|
||||
#expect(options[3] == "France 🇫🇷")
|
||||
#expect(kind == .disclosed)
|
||||
confirmation()
|
||||
return .success(())
|
||||
}
|
||||
context.send(viewAction: .submit)
|
||||
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
func testDeletePoll() async throws {
|
||||
@Test
|
||||
mutating func deletePoll() async throws {
|
||||
setupViewModel(mode: .edit(eventID: "foo", poll: .emptyDisclosed))
|
||||
|
||||
context.question = "What is your favorite country?"
|
||||
context.options.append(.init(text: "France 🇫🇷"))
|
||||
XCTAssertFalse(context.viewState.isSubmitButtonDisabled)
|
||||
#expect(!context.viewState.isSubmitButtonDisabled)
|
||||
|
||||
let deferredFailure = deferFailure(viewModel.actions, timeout: 1, message: "The alert should be shown.") { $0 == .close }
|
||||
let deferredFailure = deferFailure(viewModel.actions, timeout: .seconds(1)) { $0 == .close }
|
||||
context.send(viewAction: .delete)
|
||||
|
||||
try await deferredFailure.fulfill()
|
||||
XCTAssertNotNil(context.alertInfo, "An alert should be shown before deleting the poll.")
|
||||
#expect(context.alertInfo != nil, "An alert should be shown before deleting the poll.")
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .close }
|
||||
let expectation = XCTestExpectation(description: "Delete poll")
|
||||
timelineProxy.redactReasonClosure = { eventID, _ in
|
||||
XCTAssertEqual(eventID, .eventID("foo"))
|
||||
expectation.fulfill()
|
||||
return .success(())
|
||||
}
|
||||
context.alertInfo?.secondaryButton?.action?()
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 1)
|
||||
try await deferred.fulfill()
|
||||
try await confirmation { confirmation in
|
||||
var redactReasonCalled = false
|
||||
timelineProxy.redactReasonClosure = { eventID, _ in
|
||||
defer {
|
||||
confirmation()
|
||||
redactReasonCalled = true
|
||||
}
|
||||
#expect(eventID == .eventID("foo"))
|
||||
return .success(())
|
||||
}
|
||||
context.alertInfo?.secondaryButton?.action?()
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Since the redactReasonClosure is called asynchronously after closing the alert
|
||||
// We need to actively wait for the redactReasonClosure to be called before fulfilling the test.
|
||||
while !redactReasonCalled {
|
||||
await Task.yield()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func setupViewModel(mode: PollFormMode = .new) {
|
||||
private mutating func setupViewModel(mode: PollFormMode = .new) {
|
||||
viewModel = PollFormScreenViewModel(mode: mode,
|
||||
timelineController: MockTimelineController(timelineProxy: timelineProxy),
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
|
||||
Reference in New Issue
Block a user