0Pricing
Swift Academy · Lesson

Testing View Models

Unit test presentation logic in isolation.

Testing View Models is a free Swift Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Test ViewModels?

The ViewModel holds your presentation logic, making it the most valuable layer to unit test. Because it has no UIKit or SwiftUI dependency, you can test it in isolation, fast and deterministically.

Testable by Design

A well-built ViewModel takes its dependencies through its initializer. This lets tests substitute fakes.

final class WeatherViewModel: ObservableObject {
    @Published var temperatureText = ""
    private let service: WeatherService

    init(service: WeatherService) {
        self.service = service
    }
}

Dependency Injection

Hide concrete dependencies behind a protocol. The ViewModel depends on the abstraction, so tests can inject a mock.

protocol WeatherService {
    func fetchTemperature() async throws -> Double
}

Writing a Mock

A mock conforms to the protocol and returns canned data. It lets you control exactly what the ViewModel sees.

final class MockWeatherService: WeatherService {
    var stubbedTemperature: Double = 20
    var didCallFetch = false

    func fetchTemperature() async throws -> Double {
        didCallFetch = true
        return stubbedTemperature
    }
}

A Basic Unit Test

With the mock injected, you assert that the ViewModel transforms data correctly.

func testFormatsTemperature() async {
    let mock = MockWeatherService()
    mock.stubbedTemperature = 25
    let vm = WeatherViewModel(service: mock)

    await vm.load()

    XCTAssertEqual(vm.temperatureText, "25 C")
}

Testing State Transitions

ViewModels often expose a state enum. Test each branch by driving the inputs that produce it.

func testLoadingThenLoaded() async {
    let vm = FeedViewModel(service: MockFeedService())
    XCTAssertEqual(vm.state, .loading)

    await vm.load()

    XCTAssertEqual(vm.state, .loaded(["A", "B"]))
}

Testing Error Paths

Make the mock throw to verify the ViewModel handles failures gracefully and surfaces an error state.

final class FailingService: WeatherService {
    func fetchTemperature() async throws -> Double {
        throw URLError(.notConnectedToInternet)
    }
}

func testShowsErrorOnFailure() async {
    let vm = WeatherViewModel(service: FailingService())
    await vm.load()
    XCTAssertEqual(vm.state, .error)
}

Verifying Interactions

Sometimes you care that a dependency was called, not just the result. Spy flags on the mock let you assert behavior.

func testFetchIsCalled() async {
    let mock = MockWeatherService()
    let vm = WeatherViewModel(service: mock)
    await vm.load()
    XCTAssertTrue(mock.didCallFetch)
}

Testing @Published Output

You can subscribe to a @Published property with Combine to capture emitted values and assert the sequence.

var received: [String] = []
let cancellable = vm.$temperatureText.sink { received.append($0) }

await vm.load()

XCTAssertEqual(received.last, "25 C")
cancellable.cancel()

Keeping Tests Fast

Because mocks return instantly and there is no real network, ViewModel tests run in milliseconds. Avoid real timers and URLSession — inject fakes for clocks and schedulers too.

Best Practices

Effective ViewModel tests:

  • Inject all dependencies via protocols
  • Test one behavior per test method
  • Cover success, empty, and error paths
  • Assert on published outputs the View would read

Quick Check

Test your ViewModel testing knowledge.

Recap

You learned to unit test ViewModels in isolation:

  • Inject dependencies behind protocols
  • Use mocks to control inputs and spy on calls
  • Cover success, error, and empty states
  • Assert on @Published outputs and state transitions

Testable ViewModels are the payoff of MVVM's separation of concerns.

Frequently asked questions

Is the “Testing View Models” lesson free?

Yes — the full text of “Testing View Models” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.

What will I learn in “Testing View Models”?

Unit test presentation logic in isolation. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Testing View Models” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The MVVM Pattern
  2. Binding View Models to Views
  3. The Coordinator Pattern
  4. Testing View Models
← Back to Swift Academy