How to Generate Swift Unit Tests With Sourcegraph Cody

A practical walkthrough for using Sourcegraph Cody to generate XCTest and Swift Testing unit tests for your iOS code, then validating and refining them in Xcode.

What This Guide Covers

Writing tests is repetitive, which makes it a natural fit for AI assistance. Cody can generate unit tests for your Swift types, then you refine and run them.

This guide covers generating tests with XCTest and with the newer Swift Testing framework, and how to keep the output trustworthy.

The key mindset: Cody drafts tests, but you own them. A test you have not read and run is not a test you can rely on.

As always, Cody does not run your suite. You execute tests in Xcode or via the command line, and that is where you confirm they pass and mean something.

Pick the Right Target to Test

Start with logic-heavy, side-effect-light code. View models, formatters, mappers, and pure functions are ideal first targets.

Avoid starting with code that is tangled up in UIKit, the network, or persistence. Those need seams and mocks that make generated tests brittle.

Open the Swift file you want to test in your editor so Cody has it in context. If it depends on other types, keep those visible too.

A well-scoped target produces cleaner generated tests and teaches you how Cody handles your conventions before you tackle harder code.

Generate a First Test Pass

Select the type or function, then use Cody's test generation prompt or ask in chat: 'Write XCTest unit tests for this type covering the main cases and edge cases.'

If your project uses Swift Testing, say so explicitly: 'Use the Swift Testing framework with @Test and #expect.' Cody will follow the framework you name.

Review the generated file before saving. Check that it targets the right type, imports your module correctly, and names tests clearly.

Drop the file into your test target in Xcode. Placement in the correct target matters, or the tests will not compile against your app code.

Handle Dependencies and Mocks

Real iOS code has dependencies. Ask Cody to identify them: 'What does this class depend on, and how should I inject test doubles?'

If your type takes protocols, ask Cody to generate a mock or stub conforming to that protocol. This keeps tests fast and deterministic.

For code that reaches the network or disk directly, first ask Cody to suggest a refactor that introduces a protocol seam. Testable code usually needs a small design change.

Be explicit about your existing test helpers. If you already have mock patterns, tell Cody to reuse them so generated tests match your codebase.

Cover Edge Cases Deliberately

A first pass often tests the happy path. Push further: 'Add tests for nil input, empty collections, and boundary values.'

For error handling, ask 'Write tests that assert the correct error is thrown for invalid input.'

For async code, ask for tests using async/await or expectations, and confirm the assertions actually await the result rather than passing prematurely.

Edge cases are where tests earn their keep. Cody accelerates writing them, but you decide which cases matter for your app's behavior.

Run and Validate in Xcode

Switch to Xcode and run the new tests. Cody cannot execute them, so this step is non-negotiable.

Fix compilation issues first. Generated tests sometimes reference an initializer or property that differs slightly from your real API; correct these against the actual code.

Watch for tests that pass trivially. A test that asserts nothing meaningful gives false confidence, so read each assertion.

Run the whole suite, not just new tests, to catch interactions. You can also run tests from the command line with xcodebuild for CI integration.

Refine and De-Duplicate

Ask Cody to review its own output: 'Are any of these tests redundant, and is anything important untested?'

Remove near-duplicate cases that add maintenance cost without new coverage. Quality beats quantity in a test suite.

Ensure test names describe behavior, such as testReturnsErrorWhenTokenExpired, so failures are self-explanatory later.

Keep tests independent. Each should set up its own state and not rely on execution order, which Cody can help you verify.

Fit Tests Into Your Workflow

Adopt a loop: generate a draft with Cody, refine assertions, run in Xcode, commit. Small increments keep the suite trustworthy.

Use generated tests as a starting point for coverage on legacy code that never had any, one type at a time.

Wire your suite into continuous integration so tests run on every change, independent of whether Cody or a human wrote them.

Over time you build a habit where Cody removes the tedium of boilerplate while you keep judgment over what correctness means for your iOS app.

Describe the Behavior You Want

Sometimes the fastest route is describing the behavior you expect, then letting Cody draft the test. Tell it plainly: 'Write a test that verifies the cart total includes tax for a mixed basket.'

Describing intent rather than mechanics often yields clearer assertions, because Cody focuses on the outcome you named instead of guessing.

Review whether the generated setup actually reaches that outcome. A test can look right yet exercise the wrong path entirely.

Used this way, Cody becomes a fast translator from behavior in your head into a runnable, readable test you can refine.

Keep Tests Fast and Deterministic

Slow or flaky tests erode trust in the whole suite. When Cody generates tests, check for hidden waits, real timers, or shared global state.

Ask Cody to replace real delays with controllable test doubles, and to inject clocks or schedulers rather than calling sleep.

For anything touching randomness, seed it or stub it so results are repeatable across runs and machines.

Determinism is what lets you run the suite constantly in CI without chasing phantom failures, so it is worth insisting on from the start.

Measure Coverage, Not Just Count

A pile of tests is not the same as good coverage. Use Xcode's coverage reports to see which branches your generated tests actually exercise.

Ask Cody to target the gaps directly: 'Which branches in this function are untested, and write cases for them?'

Focus on meaningful branches, error paths, and boundary values rather than chasing a coverage percentage for its own sake.

Coverage data turns test generation from guesswork into a directed activity, with Cody filling the specific holes you have identified.

Treat Generated Tests as Living Code

Generated tests are not write-once artifacts. As your Swift types change, the tests must change with them, and stale tests are worse than none because they mislead.

When you refactor a type, ask Cody to update its tests to match the new API rather than leaving broken assertions behind.

Review generated tests in code review with the same care as production code, since a weak test can hide a real regression.

Holding tests to that standard keeps the suite an honest signal of correctness for your iOS app over time.

Frequently Asked Questions

Can Cody write both XCTest and Swift Testing tests?

Yes. Tell Cody which framework to use. For Swift Testing, ask it to use @Test and #expect; for XCTest, ask for XCTestCase subclasses and XCTAssert functions.

Will Cody run my unit tests?

No. Cody generates test code, but you run tests in Xcode or via xcodebuild. Running and reading the results is how you confirm the tests are meaningful.

How do I test code that hits the network or database?

Ask Cody to introduce a protocol seam and generate a mock conforming to it, so tests inject a test double instead of touching real network or disk.

Are Cody-generated tests reliable?

Treat them as drafts. They can reference slightly wrong APIs or assert too little. Review each assertion, run the suite, and refine before trusting the coverage.