Swift’s New Testing Framework

30 / Aug / 2024 by Pankaj Gupta 0 comments

WWDC 2024 unveiled a major improvement to XCTest, which is Swift’s Testing Framework. I found it really beneficial while working on this. So I decided to share it with all of you. Let’s begin.

Introduction

Swift Testing allows you to use the Swift programming language’s powerful and expressive capabilities to write tests with greater confidence and less code. The library works smoothly with Swift Package Manager and Xcode’s testing workflow, allowing for flexible test organization, customizable metadata, and scalable test execution

XCTest Vs Swift Testing

Now it’s time to figure out why we require the Swift Testing Framework when we already have the XCTest Framework. How does it vary from XCTest, and what are the advantages of utilizing it over XCTest?

XCTest Framework

XCTest is Apple’s default testing framework for Swift and Objective-C. It is built into Xcode and offers full support for unit, performance, and UI testing. XCTest is widely used because of its smooth interaction with Xcode, making it the preferred choice for many Swift developers.

Key Features

  1. Integration with Xcode: XCTest is strongly linked with Xcode, allowing for the simple creation of tests, execution, and debugging.
  2. Assertion Library: XCTest provides a variety of assertions to validate test scenarios. Which makes it simple to utilize.
  3. Code Coverage: Supports code coverage analysis to discover untested code.

Example:

XCTest Example

XCTest Example

Swift Testing Framework

Swift Testing Framework, also known as Swift Test, is a modern framework created exclusively for writing tests in Swift. It seeks to give a more Swift-like, expressive, and powerful method of writing tests than the existing XCTest framework.

Key Features

  1. DSL for Tests: Swift Testing Frameworks frequently employ a Domain-Specific Language (DSL) to make test creation more intuitive and readable. This can result in tests that appear more like normal language than code.
  2. First-Class Swift Support: These frameworks, which are specifically created for Swift, take advantage of the language’s characteristics like optional, generics, and type inference to deliver a smoother testing experience.
  3. Mocking and Stubbing: Many modern Swift Testing Frameworks provide support for mocking and stubbing, making it easy to separate the code being tested.
  4. Asynchronous Testing: Improved support for testing asynchronous code, which is increasingly crucial in modern Swift applications.

Benefits of Swift Testing

  1. Early Bug Detection: Writing tests helps detect bugs and obstacles early in the development process, minimizing the cost and work required to repair them later.
  2. Refactoring Confidence: Tests serve as a safety net while rewriting code, ensuring that modifications do not introduce new bugs.
  3. Documentation: Tests serve as documentation, demonstrating how the code should react in different contexts.
  4. Continuous Integration: Automated tests can be added to continuous integration (CI) pipelines, guaranteeing that code changes are tested automatically.

Add Swift Testing

Let’s understand how to add Swift Testing to an Existing Project or a new one.

1. Adding In Existing Project:- To add a new target follow the steps.
Click File -> New -> Target

Adding Target

Adding Target

2. Choose Target:- In the target popup search for unit testing and select it as shown in the image.

Unit Testing Target

Unit Testing Target

3. Target Name:- Now give the name to the target and under the testing system select Swift Testing and click on finish.

Give Name and Select Swift Testing

Give Name and Select Swift Testing

Now we have imported Test Target here and It’s time to write our first swift Test case.

Building Block of Swift Testing

1. Add Test Case:

First Swift Example

First Swift Example

As per the above example, We just need to import the Testing framework to start working with Swift Testing. To make any function Testable we just need to add the @Test attribute. After adding this attribute a diamond box was added in front to test the function.

2. Adding Expectation

After adding the Test case we have to put our expectations by using macros provided by the framework.

i. #expect:- By using this macro we can define our expectations. It’s validated whether the given condition is true or false. We don’t need to add multiple asserts to validate Bool conditions like XCAssertEqual, XCTAssertNotEqual, XCTAssertTrue, etc. Let’s understand it by example.

#expect macro uses

#expect macro uses

As we can see one success expectation and one failure expectation. Here we are Testing two cases one is equalTo and the other is greaterThan and both can be executed using #expect macro. While in XCTest we have two asserts in this case.

ii: #expect(.throws: (any Error).self):- This macro is used to expect an error to be thrown. You specify the error type or the actual error that you expect to be thrown. The test will fail if the code within the macro does not throw an error, or if the error is not what is intended. By using (any Error).self, you are indicating that the code should throw any error that confirms the Error protocol.

Example of #expect(.throws: (any Error).self)

Example of #expect (.throws: (any Error).self).

iii. #require macro:- This macro is used, as its name suggests when a method needs to run certain prerequisites before continuing with the test case. If the necessary circumstances are not met, the test fails. Let us examine an example.

Example of #require macro

Example of #require macro

In the above example, we can see that if a name is nil #require macros get failed and stop the execution of #expect macro.

Swift’s New Testing Framework has added a number of new macros. In upcoming articles, we shall talk more about that. Let us now go to another set of building blocks.

3. Traits

Traits are basically to used for defining the characteristics, attributes, or behaviors of a block of code or function. It is also used to define the descriptive information of test cases. Let’s have a look at how to define a trait.

Example of Traits

Example of Traits

As we can see in the above example when we define the traits for any test case method it replaces the name of the function with the given traits in the Left Navigation Pane. It is useful to define the cases and easy to find out and debug when it fails.

There are so many Traits that are introduced with Swift’s New Testing Framework. You can check in this Link.

4. Test Suites

Suites are basically used to combine the Test function and Suites. It can be annotated using @suite. Any Test function is implicitly considered a suite. Suites can have stored properties. It can have init and deInit methods for set-up and tear-down logic respectively. Whenever we create an object of any suite it automatically calls init() method and after Test cases run successfully it needs to be de-initialized like we were working on class and structs.  Let’s have a look at an example.

Example of Suite

Example of Suite

As we can see in the above example, All the test cases regarding Name are combined in a single suite and we have given descriptive info about the suite that will display in the left navigation pan as in the image.

Parameterized testing

Swift Testing framework allows us to pass parameters in a function that makes them testable with multiple different values. This makes tests more efficient and manageable. Let’s look an example.

Example of Parameterized Test Case

Example of Parameterized Test Case

We have created a test case that expects that the given email ID should not be nil. But at the same time, we got a compile time error which indicates that we have to pass the argument value within the @Test Attributes. Let’s correct it and see how it works.

Example of Test case with parameter

Example of a Test case with a parameter

As we can see in the given example, We are passing an array of strings and the Test function iterates each of them and validates them. It will display the result of each parameter whether that Passes or Fail and if fail then what will be the reason? We can also check the result in the Left Navigation Pan. We can re-execute only the failed argument.

Conditional Testing

While we are working big project that has several Test cases and is in maintenance mode, we get a task to change in existing behavior of the app. In this case, chances are very high to impact the test case. So We may be required to disable some test cases or only need to run in certain conditions. Here Swift Testing frameworks to help us to do this with minimal changes by using .enable or .disable attributes with @Test.

Example of .enable attribute

Example of .enable attribute

Example of .disable attribute

Example of .disable attribute

In the given example we have used .enable and .disable attributes. For using .enable attribute we need to pass a specific condition. If the condition is true it will execute else it will skip the test. Same with the disable attribute. If we add this attribute, the test case will skipped and show the description.

Conclusion

Test behaviors can be declared with less code thanks to Swift Testing’s expressive API with macros. Swift expressions and operators are used by the #expect API to validate the supplied expressions. Moreover, Swift Testing facilitates parallel testing, which raises the general productivity and efficiency of testing.

FOUND THIS USEFUL? SHARE IT

Tag -

SwiftTesting

Leave a Reply

Your email address will not be published. Required fields are marked *