.st0{fill:#FFFFFF;}

Swift Testing Template: Download My Improved Custom Template 

 June 25, 2024

by Jon Reid

0 COMMENTS

Swift Testing! The testing team at Apple has made something new… I’m quite excited, how about you? Let’s fire up Xcode 16 beta, make a new project, add a new test file… Oh. Well, that template is definitely better than the cruft we used to get. But we can do better still.

Apple’s Swift Testing Template

Let’s start by looking at the generated test file Apple gives us. Assume we’re working in a project named SampleProject, and we want to create a home for new tests. So we Command-N to make a new file, and select “Swift Testing Unit Test.”

Code snippet sample

Improve your test writing “Flow.”

Sign up to get my test-oriented code snippets.

Apple's new file template: Swift Testing Unit Test

If we enter AppleTesting as the file name, here’s what the result looks like:

//
//  AppleTests.swift
//  SampleProject
//
//  Created by Jon Reid on 6/23/24.
//
 
import Testing
 
struct AppleTests {
 
    @Test func <#test function name#>() async throws {
        // Write your test here and use APIs like `#expect(...)` to check expected conditions.
    }
 
}

You may be asking, what’s wrong with that?

For starters, there’s the file comment block cruft. Unless I’m releasing files to the public, I see no value in starting files with comment blocks.

Then we come to the import statements. Or rather, the one import statement. Aren’t we missing something? Right, the @testable import of our source module is missing.

Finally, we come to the test itself. What’s there is a vast improvement over the old setUpWithError, tearDownWithError, an empty test with a long explanation, and a performance test. The empty test is a great start.

My Swift Testing Template

Now let me show you what happens if we make a new file using my new Swift Testing file template. First, we select “Swift Testing (Jon Reid)”.

Swift Testing template by Jon Reid

Unlike Apple’s file template which suggests “Test” as the file name, my template suggests the plural “Tests.” I like that better since a file usually contains multiple test cases. To finish off the file name, let’s move the cursor to the beginning and enter “QualityCoding” before “Tests.” That creates this file:

@testable import SampleProject
import Testing
 
final class QualityCodingTests: @unchecked Sendable {
    @Test
    func zero() async throws {
        Issue.record("Tests not yet implemented in QualityCodingTests")
    }
}

Check it out:

No File Comment Block

The file doesn’t start with a file comment block. There’s nothing to delete.

Useful import Statements

The file template makes an educated guess about the name of your production code module. It assumes it’s the same as your project name.

While this isn’t always true, it shows you that you should use @testable import to access your production code.

Class, Not Struct

At first, it seems delightful to declare a test suite as a struct. But since each test is instantiated and destroyed separately, there is no benefit to using a value object. Using a class to hold your test suite lets you:

  • Add mutable properties so that test spies can capture values.
  • Add a deinit to do tear-down if you ever need it.

Sendable (Unchecked Is Safe)

If you want your test suite to capture values, you need mutable properties. With strict concurrency warnings, Xcode will complain that your test class is non-sendable. No worries — my template declares it @unchecked Sendable. This is safe because a separate instance is created for each test. There is no sharing of the test suite across tests.

You may be wondering, why use a class that’s been declared Sendable? We can avoid strict concurrency warnings by just using a struct, right?

Yes, you can. But as soon as you need to add mutable properties, or a deinit to do tear-down, you’ll have to change it.

I’m lazy. I’d rather not have to change the declaration of the suite when I want it to do more. This is the same reason I declare test methods as async throws whether I need it or not. I want the ability to change my mind while minimizing rework.

Test Zero

And instead of an empty test, it starts with “Test Zero.” This comes from my book iOS Unit Testing by Example: I want to confirm the test infrastructure before we add any new tests. Test Zero arrives, and it’s supposed to fail. As I write in my book,

This is an example of a larger principle: take a small step, get feedback.

Once you see Test Zero fail, then it’s safe to delete it.

Want to try my file template for Swift Testing? Download it here:

How to Set up GitHub Actions for CI with Xcode

Jon Reid

Programming was fun when I was a kid. But working in Silicon Valley, I saw poor code led to fear, with real human costs. Looking for ways to make my life better, I learned about Extreme Programming, including unit testing, test-driven development (TDD), and refactoring. Programming became fun again! I've now been doing TDD in Apple environments for 20 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy. Now a coach with Industrial Logic!

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
>