Let's think about it this way for a second
A unit test tests an individual function/class (ViewModel logic, data transformation) without needing UI/device — it uses the XCTest framework (built into Xcode), runs fast, and can run frequently in a CI pipeline. A UI test (XCUITest), on the other hand, automates actual UI interaction (button taps, text input) on the Simulator — you can use `XCUIApplication` to verify things like 'does the text change when I tap this button'.
Let's connect this to a real-world scenario
To unit test the logic of `TodoViewModel.addTodo()`, you could write `func testAddTodo_increasesListSize() { viewModel.addTodo(title: "Test"); XCTAssertEqual(viewModel.todos.count, 1) }` — for a UI test, you could write `app.buttons["Add"].tap(); XCTAssertTrue(app.staticTexts["Test"].exists)` to verify that the todo item actually shows up on screen after tapping the button.
Let's look at it together
// Unit test
import XCTest
@testable import MyApp
final class TodoViewModelTests: XCTestCase {
func testAddTodo_increasesListSize() {
let viewModel = TodoViewModel()
viewModel.addTodo(title: "Buy milk")
XCTAssertEqual(viewModel.todos.count, 1)
}
}
// UI test
final class TodoAppUITests: XCTestCase {
func testTappingAddButton_showsNewTodo() {
let app = XCUIApplication()
app.launch()
app.buttons["Add"].tap()
XCTAssertTrue(app.staticTexts["Buy milk"].exists)
}
}$ xcodebuild test
Test Suite 'All tests' passed — 2 tests, 0 failures5-minute try-it
Write a unit test yourself for `TodoViewModel`'s `addTodo()` function, and run it with Xcode's 'Run Test' (◇ icon) button.
A quick heads-up
Don't aim for 100% test coverage — prioritize testing critical business logic (payment, data integrity); tests for UI details (color, spacing) tend to have a low ROI.