Swift Testing (finally! 🥳)

David Martinez
6 min readJul 29, 2024

Finally! Apple has included new tools for building unit testing for Swift 🎉.

After watching the WWDC videos and various tutorials about this new framework, I pondered what approach to take for this article and, finally, we’re going to try to give it a practical focus by attempting to answer the following questions…

  • What new features does it offer that the XCTest framework doesn’t already provide?
  • Will it help in my daily workflow? Should I start working with it?

What’s new?

As a developer who might be working in a development team, you receive this new framework and think… what do I do with it now?

Let’s go over a list of things you can now do with Swift Testing that you couldn’t with XCTest (remember, there are likely many more — I’d love for you to add examples in the comments to keep expanding this article 🙂):

  • You can fully choose the test name thanks to the use of the Test macro. The Test macro supports some arguments (Traits) allowing us to alter test properties (add tags, conditionally disable tests, pass parameters, and so on).
  • Tests can now be global (they don’t have to be tied to a class).
  • Tests can be grouped in a struct, class, or actor using the concept of Suite. A Suite can have a series of properties that affect all associated tests (the same properties as the Test macro).
  • Simplification of all assertion macros (e.g., XCTAssertXXXX, etc.) to just two: #expect and #require.
  • Test arguments, that allow us to test a method using multiple paremeters and each one will be a separated execution.
  • Cross platform testing framework for Swift.

And in my daily workflow, what effect does it have?

Let’s look at some examples of situations where the new framework might benefit you compared to the usual xctest format.

Case 1: Naming tests

Include customized and readable names and descriptions. Being able to choose the name and customize its description in Xcode can be significantly helpful for maintenance improvement. Let’s look at an example that contrasts the traditional method with the new one:

Customizing name of the test methods appearance

In the left column, you can see how the test name is based on what we can manually specify in the test.

Case 2: Tests … that fail :(

How many times you have in your code this little nightmare called … a BUG 🐞!

Another bug in my code

… You know there’s a problem in the code, but the team decides to go to production with it (it might be minor). The situation is that tests are failing but … what do you decide? Do you leave them commented out? Do you delete them? Do you leave them failing?

Swift Testing offers options (through a Trait added to the Test or Suite macro) to decide whether a test should be executed or not. Let’s look at an example:

Disabling test using Traits

By adding the “disabled” property, the test (cyan arrow) will simply not execute and will be marked as disabled. Whereas in the traditional mode, it just fails. We’ve also added the “bug” option, allowing us to add a descriptive text, for instance, the Jira bug we’ve created to address in the next iteration (because you will fix it, right? … right?).

Case 3 — Migration:

Alright, so we’re going to migrate to using Swift Testing. How much will it cost? This question is tricky and obviously depends on each development team. The advantage is you can do a gradual migration since Swift Testing is fully compatible with XCTest.

My recommendation is to analyze the framework and evaluate (in terms of time/effort):

  • Leave the existing tests and do everything new with Swift Testing.
  • Design a migration plan from XCTestSwift Testing for all your tests.
  • Continue using XCTest.

As I said, there’s no one-size-fits-all solution. Be smart, measure potential outcomes, and go with what best suits your team and situation.

For the migration, it might be interesting to design a good prompt for GPT or another AI that can lay a solid foundation for migration.

Case 4 — Grouping tests

You’re coding in a section of your application and finish your unit tests for the parts you’re working on. The problem arises with integration tests, maybe … have I broken some part with these modifications? In these situations (depending on your project or test plans), you might need to run all your tests to verify everything is okay, and this operation could cost a lot of time.

Focusing on an example, it’s like if in your newspaper app, you’re modifying the subscription process and need to run tests for the news grid, the app menu, or offline article downloads.

It could be cool if we could apply a tag to the tests created by us and filter them, right? Allowing us to group our tests using some type of semanthics based on our custom project.

Well, that’s exactly what’s been implemented through another Trait 🎉.

Grouping tests by tag

Let’s deep step-by-step:

// 1 → We created two tags, articles and user, by extending the Tag class
// 2 → We linked a tag to the Suites, implying all their tests inherit those tags.
// 3 → We added an additional “articles” Tag to the test “main_articles_of_a_user”.

At this point, if you notice, Xcode groups these tests by Tag. This allows us to know, semantically, all tests related to articles or users and enables running them as a group by clicking the play button next to the tag:

Execute Test by Tag

Case 5: Method Arguments

Let’s move on to the last case we’ll analyze today. What happens when I want to run tests with multiple data sets? How are they visualised? As in the previous cases… let’s see an example!

Currently, you might find something like this:

We simply want to test our “sut” (Subject under test) with two inputs:

  • An empty array
  • An array with the sports tag.

The problem here is that when one of these data sets fails, it visually tends to be quite confusing and isn’t usually easy to see which data set failed (could be multiple of them).

Now let’s see the same example with Swift Testing by comparing one code with XCTest:

Swift Testing, adding arguments

// 1 → On one hand, we add a parameter to our test method. This enables us to pass parameters for its execution.
// 2 → We add each argument we want to test our code with.

After execution, we can see in the left execution column right below our “Load And Filter Articles By Tag”, two sub-items appear indicating with which argument we’ve run and if test passed or not for each data.

At this point, I’d like to add another tip. Swift Testing has introduced a new protocol for tests called CustomTestStringConvertible. Shall we see it? Let’s make our Article.Tag class implement it and re-run the test:

Using CustomTestStringConvertible

As we can see, this part allows us to choose the name displayed in the Xcode panels. It functions similarly to the debugDescription property of NSObject.

Anything else to share?

Of course! Besides everything we’ve seen so far, here are some interesting facts about Swift Testing:

  • It is a multi-platform framework for Swift, meaning it can run on Unix, for example.
  • It allows parallel tests on devices (and this is completely new).
  • There’s no support for XCUITest yet.
  • You need Swift 6.

I hope you found this useful!

Happy coding 🎉 and enjoy the Olympic Games!

Bibliography

--

--

No responses yet