{"id":47271,"date":"2025-04-01T16:23:46","date_gmt":"2025-04-01T20:23:46","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=47271"},"modified":"2025-07-14T13:46:08","modified_gmt":"2025-07-14T17:46:08","slug":"swift-testing-test-scoping-traits","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2025\/04\/01\/swift-testing-test-scoping-traits\/","title":{"rendered":"Swift Testing: Test Scoping Traits"},"content":{"rendered":"<p><a href=\"https:\/\/www.swift.org\/blog\/swift-6.1-released\/\">Holly Borla<\/a>:<\/p>\n<blockquote cite=\"https:\/\/www.swift.org\/blog\/swift-6.1-released\/\">\n<p>Swift 6.1 enables custom Swift Testing traits to perform logic before or after tests run in order to share set-up or tear-down logic. If you write a custom trait type which conforms to the new <code>TestScoping<\/code> protocol, you can implement a method to customize the scope in which each test or suite the trait is applied to will execute. For example, you could implement a trait which binds a task local value to a mocked resource:<\/p>\n<pre>struct MockAPICredentialsTrait: TestTrait, TestScoping {\n  func provideScope(for test: Test, testCase: Test.Case?, performing function: @Sendable () async throws -> Void) async throws {\n    let mockCredentials = APICredentials(apiKey: \"fake\")\n    try await APICredentials.$current.withValue(mockCredentials) {\n      try await function()\n    }\n  }\n}\n\nextension Trait where Self == MockAPICredentialsTrait {\n  static var mockAPICredentials: Self { Self() }\n}\n\n@Test(.mockAPICredentials)\nfunc example() {\n  \/\/ Validate API usage, referencing `APICredentials.current`...\n}<\/pre>\n<\/blockquote>\n<p>Because task locals cannot be separated into set-up and tear-down portions, they were previously only usable in Swift Testing if you repeated them within each test function. <code>TestScoping<\/code> gets rid of the indentation that entails and can also be applied at the suite level, so you don&rsquo;t have to repeat it for each test. It&rsquo;s great to have this functionality, though I think the implementation via traits feels a bit convoluted compared with XCTest&rsquo;s <code>invokeTest()<\/code>.<\/p>\n\n<p><a href=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/main\/proposals\/testing\/0007-test-scoping-traits.md\">ST-0007<\/a>:<\/p>\n<blockquote cite=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/main\/proposals\/testing\/0007-test-scoping-traits.md\"><p>Some test authors have expressed interest in allowing custom traits to access\nthe instance of a suite type for <code>@Test<\/code> instance methods, so the trait could\ninspect or mutate the instance. Currently, only instance-level members of a\nsuite type (including <code>init<\/code>, <code>deinit<\/code>, and the test function itself) can access\n<code>self<\/code>, so this would grant traits applied to an instance test method access to\nthe instance as well. This is certainly interesting, but poses several technical\nchallenges that puts it out of scope of this proposal.<\/p><\/blockquote>\n<p>It seems like a major limitation that any code factored out in this way can&rsquo;t access properties or helper functions in the type that contains the test.<\/p>\n\n<blockquote cite=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/main\/proposals\/testing\/0007-test-scoping-traits.md\"><p>Some reviewers of this proposal pointed out that the hypothetical usage example shown earlier involving setting a task local value while a test is executing will likely become a common use of these APIs. To streamline that pattern, it would be very natural to add a built-in trait type which facilitates this. I have prototyped this idea and plan to add it once this new trait functionality lands.<\/p><\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2025\/04\/01\/xcode-16-3\/\">Xcode 16.3<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2025\/03\/03\/swift-6-1\/\">Swift 6.1<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/12\/17\/issues-adopting-swift-testing\/\">Issues Adopting Swift Testing<\/a><\/li>\n<\/ul>\n\n<p id=\"swift-testing-test-scoping-traits-update-2025-04-02\">Update (2025-04-02): <a href=\"https:\/\/mastodon.social\/@ctietze\/114260919230044068\">Christian Tietze<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@ctietze\/114260919230044068\">\n<p>I really like how Swift Testing Traits turns into how Emacs Lisp implements <a href=\"https:\/\/www.gnu.org\/software\/emacs\/manual\/html_node\/elisp\/Advising-Functions.html\">function advising<\/a>.<\/p>\n<p>You basically decorate a function call with a macro.<\/p>\n<p>Unlike (Emacs) Lisp, you can't fiddle with the context of the called function and pass data along.<\/p>\n<\/blockquote>\n\n<p id=\"swift-testing-test-scoping-traits-update-2025-04-22\">Update (<a href=\"#swift-testing-test-scoping-traits-update-2025-04-22\">2025-04-22<\/a>): <a href=\"https:\/\/swiftwithmajid.com\/2025\/04\/15\/introducing-swift-testing-scoping\/\">Majid Jabrayilov<\/a>:<\/p>\n<blockquote cite=\"https:\/\/swiftwithmajid.com\/2025\/04\/15\/introducing-swift-testing-scoping\/\">\n<p>In our example, we run the performing function inside the closure that overrides the task local value and set the environment to the mocked instance. This technique allows us to run the test suite or test function and provide it with a mocked environment.<\/p>\n<\/blockquote>","protected":false},"excerpt":{"rendered":"<p>Holly Borla: Swift 6.1 enables custom Swift Testing traits to perform logic before or after tests run in order to share set-up or tear-down logic. If you write a custom trait type which conforms to the new TestScoping protocol, you can implement a method to customize the scope in which each test or suite the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2025-04-01T20:23:48Z","apple_news_api_id":"c72e16ec-3f6e-4189-80b5-7d5165212600","apple_news_api_modified_at":"2025-04-22T17:19:18Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAQ==","apple_news_api_share_url":"https:\/\/apple.news\/Axy4W7D9uQYmAtX1RZSEmAA","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[71,2200,901,2796,268],"class_list":["post-47271","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-programming","tag-swift-concurrency","tag-swift-programming-language","tag-swift-testing","tag-testing"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/47271","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=47271"}],"version-history":[{"count":4,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/47271\/revisions"}],"predecessor-version":[{"id":47454,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/47271\/revisions\/47454"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=47271"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=47271"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=47271"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}