[{"content":"At this year\u0026rsquo;s WWDC, Apple introduced a major visual update the \u0026ldquo;Liquid Glass\u0026rdquo; design - a dynamic new material arriving with iOS 26. Yes, you read that right: all the rumours were true, and we\u0026rsquo;re jumping straight from iOS 18 to iOS 26.\nHow do I feel about the new design? Honestly, I like it. I did a small side by side comparison using Simulator between iOS 18 and iOS 26. The new look speaks to me more. It feels more modern\u0026hellip;\nAt this point, the design has won me over\u0026hellip; but that might just be the novelty effect. I\u0026rsquo;ve seen some concerns online about lack of readability in the new interface. I admit that I haven\u0026rsquo;t had a chance yet to try it on a real device (I\u0026rsquo;m not brave enough to install the very first beta version on my personal iPhone 😅), but I\u0026rsquo;m really looking forward exploring it more!\nAlthough I haven\u0026rsquo;t updated to the new system yet, I did get my hands on the latest Xcode (luckily it works with macOS 15.4) and some of the new beta APIs. I was especially curious to try something with Liquid Glass design and ended up experimenting with the new GlassEffectContainer.\nI built a small demo with an expandable / collapsible menu containing four items plus an action button.\nThe button changes its icon when tapped and reveals menu items. All items are wrapped in GlassEffectContainer. Each item uses the new glassEffect modifier. In some menu variations, items are grouped into sets of 2, 3 or 4. using the glassEffectUnion modifier, which creates a single, unified glass effect across multiple views. Each version of menu experiments with different GlassEffectContainer spacing, HStack/VStack spacing values. Showtime! 📹\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/wwdc-2025-liquid-glass-design/","summary":"\u003cp\u003eAt this year\u0026rsquo;s WWDC, Apple introduced a major visual update the \u0026ldquo;Liquid Glass\u0026rdquo; design - a dynamic new material arriving with iOS 26. Yes, you read that right: all the rumours were true, and we\u0026rsquo;re jumping straight from iOS 18 to iOS 26.\u003c/p\u003e\n\u003cp\u003eHow do I feel about the new design?\nHonestly, I like it. I did a small side by side comparison using Simulator between iOS 18 and iOS 26. The new look speaks to me more. It feels more modern\u0026hellip;\u003c/p\u003e","title":"Say hello to the Apple's new Liquid Glass design"},{"content":"New possibilities have arrived! 🛬\nThis year, Apple released 14 new beta frameworks, bringing powerful tools for product builders to craft outstanding features.\nAs the platform grows, so does its complexity. Don\u0026rsquo;t worry! You don\u0026rsquo;t have to master all of them at once (most you may never even use). Instead, focus on understanding each framework\u0026rsquo;s core purpose. This will broaden your awareness of available tooling and help you later assess which framework you need to solve a given problem - you can dive deeper when the need comes.\nMy top pick this year is Foundation Models 🤖\nWhy this one?\nThere\u0026rsquo;s no doubt that AI and large language models have been revolutionising our daily lives. This framework enables on-device models specialised in language understanding - perfect for privacy-respecting apps that keep all processing local, without leaking your private data to the cloud. I\u0026rsquo;m eager to experiment with it in a side project and share more thoughts soon!\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/wwdc-2025-new-possibilities-have-arrived/","summary":"\u003cp\u003eNew possibilities have arrived! 🛬\u003c/p\u003e\n\u003cp\u003eThis year, Apple released 14 new beta frameworks, bringing powerful tools for product builders to craft outstanding features.\u003c/p\u003e\n\u003cp\u003eAs the platform grows, so does its complexity. Don\u0026rsquo;t worry! You don\u0026rsquo;t have to master all of them at once (most you may never even use). Instead, focus on understanding each framework\u0026rsquo;s core purpose. This will broaden your awareness of available tooling and help you later assess which framework you need to solve a given problem - you can dive deeper when the need comes.\u003c/p\u003e","title":"WWDC25 - New possibilities have arrived!"},{"content":"Intro All across iOS dev blogs and posts you read about AppDelegate being deprecated in the next major iOS release.\nAppDelegate has been a go-to for app lifecycle handling since iOS 2. From iOS 13, SceneDelegate lives alongside AppDelegate handling per-scene lifecycle. Do the methods applicationWillEnterForeground and sceneWillEnterForeground actually behave the same?\nDifference 1 - App-wide vs per-scene calls AppDelegate’s will enter foreground Fires once when the entire app moves from background to foreground SceneDelegate’s will enter foreground Fires once per scene as each one enters foreground This behaviour is reflected in each method’s signature (look on arguments) ⤵️\n// AppDelegate func applicationWillEnterForeground(_ application: UIApplication) {} // SceneDelegate func sceneWillEnterForeground(_ scene: UIScene) {} Difference 2 - App start behaviour On a fresh lunch:\nAppDelegate’s will enter foreground function is not called SceneDelegate’s will enter foreground function is called Takeaways Migrate with care - when transferring logic from AppDelegate to SceneDelegate, remember it now runs per scene and may fire multiple times. Do not assume - even though both delegates exposes “will enter foreground” method, their behaviour isn’t identical (app start behaviour 👀). Remember - AppDelegate will still stick around for a while. You don’t need to panic and plan ASAP refactors. Always check docs and verify behaviour in your app before making crucial changes.\nPDF version ⤵️ Will enter foreground or won\u0026rsquo;t?\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/will-it-enter-foreground-or-wont/","summary":"\u003ch2 id=\"intro\"\u003eIntro\u003c/h2\u003e\n\u003cp\u003eAll across iOS dev blogs and posts you read about AppDelegate being deprecated in the next major iOS release.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAppDelegate has been a go-to for app lifecycle handling since iOS 2.\u003c/li\u003e\n\u003cli\u003eFrom iOS 13, SceneDelegate lives alongside AppDelegate handling per-scene lifecycle.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eDo the methods \u003ccode\u003eapplicationWillEnterForeground\u003c/code\u003e and \u003ccode\u003esceneWillEnterForeground\u003c/code\u003e actually behave the same?\u003c/p\u003e\n\u003ch2 id=\"difference-1---app-wide-vs-per-scene-calls\"\u003eDifference 1 - App-wide vs per-scene calls\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eAppDelegate\u003c/code\u003e’s will enter foreground\n\u003cul\u003e\n\u003cli\u003eFires once when the entire app moves from background to foreground\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eSceneDelegate\u003c/code\u003e’s will enter foreground\n\u003cul\u003e\n\u003cli\u003eFires once per scene as each one enters foreground\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis behaviour is reflected in each method’s signature (look on arguments) ⤵️\u003c/p\u003e","title":"Will enter foreground or won't?"},{"content":"See how you can wrap any singleton behind a protocol to make it injectable and your code fully testable 💯\nThe blog post shows how to deal with URLSession.shared usage.\nThe same strategy can be applied to all other singletons in your code!\nThe problem Service uses URLSession.shared directly. Tight coupling makes unit testing impossible without real network calls. struct PostsAPISerivce { func fetchPosts() async throws -\u0026gt; [Post] { let url = URL(string: \u0026#34;.../posts\u0026#34;)! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode([Post].self, from: data) } } In the sections, you’ll get step-by-step guide of making this service testable. These same steps can be applied to all other singletons found in your code.\nStep 1: Inspect the used API CMD-click on data(from: url) to view the documentation /// Convenience method to load data using a URL, creates /// and resumes a URLSessionDataTask internally. /// /// - Parameter url: The URL for which to load data. /// - Parameter delegate: Task-specific delegate. /// - Returns: Data and response. public func data( from url: URL, delegate: (any URLSessionTaskDelegate)? = nil ) async throws -\u0026gt; (Data, URLResponse) Step 2: Define a protocol Create URLSessionProtocol Copy the function signature from the documentation into your protocol definition. Remove default arguments (not allowed in protocol). protocol URLSessionProtocol { func data( from url: URL, delegate: (any URLSessionTaskDelegate)? ) async throws -\u0026gt; (Data, URLResponse) } Step 3: Conform URLSession to URLSessionProtocol Add the following extension to make URLSession conforms to your protocol extension URLSession: URLSessionProtocol {} Step 4: Inject Dependecy Refactor the service and inject URLSessionProtocol into it. struct PostsAPISerivce { private let urlSession: URLSessionProtocol init(urlSession: URLSessionProtocol) { self.urlSession = urlSession } func fetchPosts() async throws -\u0026gt; [Post] { let url = URL(string: \u0026#34;.../posts\u0026#34;)! let (data, _) = try await urlSession.data(from: url, delegate: nil) return try JSONDecoder().decode([Post].self, from: data) } } Now a Spy or Mock conforming to URLSessionProtocol can be created and injected into PostsAPIService to simulate API responses.\nSummary Benefits:\nNo real API calls in tests ✅ Spies and Mocks can be injected in tests to control API responses (successes \u0026amp; errors) ✅ Reusable for all other components requiring URLSession ✅ Remember - These same steps can be applied to all other singletons found in your code.\nPDF version ⤵️ Turning Singleton Usage into Testable Code\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/turning-singleton-usage-into-testable-code/","summary":"\u003cp\u003eSee how you can wrap any singleton behind a protocol to make it injectable and your code fully testable 💯\u003c/p\u003e\n\u003cp\u003eThe blog post shows how to deal with URLSession.shared usage.\u003c/p\u003e\n\u003cp\u003eThe same strategy can be applied to all other singletons in your code!\u003c/p\u003e\n\u003ch2 id=\"the-problem\"\u003eThe problem\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eService uses \u003ccode\u003eURLSession.shared\u003c/code\u003e directly.\u003c/li\u003e\n\u003cli\u003eTight coupling makes unit testing impossible without real network calls.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePostsAPISerivce\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efetchPosts\u003c/span\u003e() async \u003cspan style=\"color:#66d9ef\"\u003ethrows\u003c/span\u003e -\u0026gt; [Post] {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e url = URL(string: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;.../posts\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e (data, \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e) = \u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e await URLSession.shared.data(from: url)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e JSONDecoder().decode([Post].\u003cspan style=\"color:#66d9ef\"\u003eself\u003c/span\u003e, from: data)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the sections, you’ll get step-by-step guide of making this service testable. These same steps can be applied to all other singletons found in your code.\u003c/p\u003e","title":"Turning Singleton Usage into Testable Code"},{"content":"Intro If you\u0026rsquo;re able to solve this, it means you understand TaskLocal really well.\nRiddle Look at the attached code snippet and guess:\nWhat will be printed at each step? Is the order of prints always the same? class Riddler { @TaskLocal static var message = \u0026#34;Hello\u0026#34; static func riddleMe() { Self.$message .withValue(\u0026#34;Bye\u0026#34;, operation: { print(\u0026#34;Print 1: \\(Self.message)\u0026#34;) // ??? Task { print(\u0026#34;Print 2: \\(Self.message)\u0026#34;) // ??? } Task.detached { print(\u0026#34;Print 3: \\(Self.message)\u0026#34;) // ??? } }) print(\u0026#34;Print 4: \\(Self.message)\u0026#34;) // ??? } } Riddler.riddleMe() Hint Do you want to learn more about TaskLocal and Test Scoping in Swift 6.1 first? - Check out my blog post on \u0026ldquo;Concurrency-Safe Testing in Swift 6.1 with TaskLocal and Test Scoping\u0026rdquo; HERE.\nAnswer class Riddler { @TaskLocal static var message = \u0026#34;Hello\u0026#34; static func riddleMe() { Self.$message .withValue(\u0026#34;Bye\u0026#34;, operation: { print(\u0026#34;Print 1: \\(Self.message)\u0026#34;) // Bye Task { print(\u0026#34;Print 2: \\(Self.message)\u0026#34;) // Bye } Task.detached { print(\u0026#34;Print 3: \\(Self.message)\u0026#34;) // Hello } }) print(\u0026#34;Print 4: \\(Self.message)\u0026#34;) // Hello } } Riddler.riddleMe() Console output\nPrint 1: Bye Print 2: Bye Print 3: Hello Print 4: Hello Explanation Print 1 - you\u0026rsquo;re inside the .withValue(\u0026quot;Bye\u0026quot;) block. The message TaskLocal property is overriden bt \u0026ldquo;Bye\u0026rdquo; Print 2 - the non-detached Task { ... } inherits the parent tasks\u0026rsquo;s context, including TaskLocal override. Print 3 - the detached task runs with a new context - it does not inherit any TaskLocal overrides. Print 4 - outside the withValue block, the override reverts to the default \u0026ldquo;Hello\u0026rdquo; value Is the order of prints always the same? - No\nWhy?\nIn the code snippet we spawn 2 tasks: a child Task and just after a dettached Task. Both run concurrently, so we have no control over when they start or complete. This means you can see the different output in the console for each code run.\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/task-local-riddle/","summary":"\u003ch2 id=\"intro\"\u003eIntro\u003c/h2\u003e\n\u003cp\u003eIf you\u0026rsquo;re able to solve this, it means you understand TaskLocal really well.\u003c/p\u003e\n\u003ch2 id=\"riddle\"\u003eRiddle\u003c/h2\u003e\n\u003cp\u003eLook at the attached code snippet and guess:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWhat will be printed at each step?\u003c/li\u003e\n\u003cli\u003eIs the order of prints always the same?\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRiddler\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    @TaskLocal \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e message = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Hello\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eriddleMe\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eSelf\u003c/span\u003e.\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emessage\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          .withValue(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Bye\u0026#34;\u003c/span\u003e, operation: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Print 1: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\\(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eSelf\u003c/span\u003e.message\u003cspan style=\"color:#e6db74\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#75715e\"\u003e// ???\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              Task {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                  print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Print 2: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\\(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eSelf\u003c/span\u003e.message\u003cspan style=\"color:#e6db74\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#75715e\"\u003e// ???\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              Task.detached {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Print 3: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\\(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eSelf\u003c/span\u003e.message\u003cspan style=\"color:#e6db74\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#75715e\"\u003e// ???\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Print 4: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\\(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eSelf\u003c/span\u003e.message\u003cspan style=\"color:#e6db74\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#75715e\"\u003e// ???\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRiddler.riddleMe()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"hint\"\u003eHint\u003c/h3\u003e\n\u003cp\u003eDo you want to learn more about TaskLocal and Test Scoping in Swift 6.1 first? - Check out my blog post on \u0026ldquo;Concurrency-Safe Testing in Swift 6.1 with TaskLocal and Test Scoping\u0026rdquo; \u003ca href=\"https://www.mobiledevdiary.com/posts/concurency-safe-testing-in-swift-6-1/\"\u003eHERE\u003c/a\u003e.\u003c/p\u003e","title":"Swift Concurrency Riddle - TaskLocal"},{"content":"Today’s example shows a static property providing the current date.\nPerfect mechanism when we don’t want to inject it everywhere where it’s used.\nHow to test it? Check it out ⤵️\nInitial setup let currentDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .none return formatter }() var currentDateFormatted: String { currentDateFormatter.string(from: DateEnvironment.currentDate()) } enum DateEnvironment { static var currentDate: () -\u0026gt; Date = Date.init } extension Date { // 16.04.2025 static let sixteenthOfApril = Date(timeIntervalSince1970: 1744840515) } Old way - XCTest Override the static property in tests. Works, but not concurrency-safe (Ok, for XCTest because test ran serially). class CurrentDateFormatterTests: XCTestCase { func test_dateFormatting() { // ✅ DateEnvironment.currentDate = { .sixteenthOfApril } XCTAssertEqual(currentDateFormatted, \u0026#34;16.04.2025\u0026#34;) } } Swift Testing before Swift 6.1 enum DateEnvironment { @TaskLocal static var currentDate: () -\u0026gt; Date = Date.init } struct CurrentDateFormatterTests { @Test func dateFormatting() { // ✅ DateEnvironment.$currentDate .withValue({ .sixteenthOfApril }) { #expect(currentDateFormatted == \u0026#34;16.04.2025\u0026#34;) } } } Why @TaskLocal?\nScoped per async Task. No global side-effects Safe for parallel tests What is @TaskLocal? Property wrapper for per-Task storage. Default value is returned when no override is applied. withValue(_:, operation:) - binds a new value just for that Task. Child Task { \u0026hellip; } inherits override. Detached - Task.detached { \u0026hellip; } does not inherit override. DateEnvironment.$currentDate.withValue({ .sixteenthOfApril }) { print(DateEnvironment.currentDate()) // 16.04.2025 Task { print(DateEnvironment.currentDate()) // 16.04.2025 (inherited) } Task.detached { print(DateEnvironment.currentDate()) // today’s date (no inheritance) } } New in Swift 6.1: Test Scoping in Action enum DateEnvironment { @TaskLocal static var currentDate: () -\u0026gt; Date = Date.init } struct FixedDateTrait: TestTrait, TestScoping { let date: Date func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -\u0026gt; Void ) async throws { try await DateEnvironment.$currentDate .withValue({ date }, operation: function) } } extension Trait where Self == FixedDateTrait { static func fixedDate(_ date: Date) -\u0026gt; Self { .init(date: date) } } struct CurrentDateFormatterTests { @Test(.fixedDate(.sixteenthOfApril)) func dateFormatting() { // ✅ #expect(currentDateFormatted == \u0026#34;16.04.2025\u0026#34;) } } Final advice Stay Curious\nUse @TaskLocal and Test Scoping for deterministic, parallel-safe tests. Enjoy faster feedback looks and cleaner code.\nRemember - Learning never stops - keep experimenting with new features!\nPDF version ⤵️\nConcurrency-Safe Testing in Swift 6.1 with @TaskLocal and Test Scoping\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/concurency-safe-testing-in-swift-6-1/","summary":"\u003cp\u003eToday’s example shows a static property providing the current date.\u003c/p\u003e\n\u003cp\u003ePerfect mechanism when we don’t want to inject it everywhere where it’s used.\u003c/p\u003e\n\u003cp\u003eHow to test it? Check it out ⤵️\u003c/p\u003e\n\u003ch2 id=\"initial-setup\"\u003eInitial setup\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e currentDateFormatter: DateFormatter = {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e formatter = DateFormatter()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    formatter.dateStyle = .short\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    formatter.timeStyle = .\u003cspan style=\"color:#66d9ef\"\u003enone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e formatter\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e currentDateFormatted: String {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    currentDateFormatter.string(from: DateEnvironment.currentDate())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eenum\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDateEnvironment\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e currentDate: () -\u0026gt; Date = Date.\u003cspan style=\"color:#66d9ef\"\u003einit\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eextension\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDate\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 16.04.2025\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e sixteenthOfApril = Date(timeIntervalSince1970: \u003cspan style=\"color:#ae81ff\"\u003e1744840515\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"old-way---xctest\"\u003eOld way - XCTest\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eOverride the static property in tests.\u003c/li\u003e\n\u003cli\u003eWorks, but not concurrency-safe (Ok, for XCTest because test ran serially).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eCurrentDateFormatterTests\u003c/span\u003e: XCTestCase {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etest_dateFormatting\u003c/span\u003e() { \u003cspan style=\"color:#75715e\"\u003e// ✅\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        DateEnvironment.currentDate = { .sixteenthOfApril }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        XCTAssertEqual(currentDateFormatted, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;16.04.2025\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"swift-testing-before-swift-61\"\u003eSwift Testing before Swift 6.1\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eenum\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDateEnvironment\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    @TaskLocal \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e currentDate: () -\u0026gt; Date = Date.\u003cspan style=\"color:#66d9ef\"\u003einit\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eCurrentDateFormatterTests\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    @Test\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edateFormatting\u003c/span\u003e() { \u003cspan style=\"color:#75715e\"\u003e// ✅\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        DateEnvironment.\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ecurrentDate\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            .withValue({ .sixteenthOfApril }) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                #expect(currentDateFormatted == \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;16.04.2025\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhy @TaskLocal?\u003c/p\u003e","title":"Concurrency-Safe Testing in Swift 6.1 with @TaskLocal and Test Scoping"},{"content":"Mind your argument names in Swift Testing\u0026rsquo;s parametrised tests!\nAs a follow up to my recent post on refactoring to use Swift Testing\u0026rsquo;s parametrised tests, I\u0026rsquo;m diving into the crucial - yet often overlooked topic of how to name your parametrised test inputs.\nOption 1: First Named Tuples Only the first tuple is named, all others rely on positional matching to (a, b, result).\n✅ Minimal boilerplate for small input sets ❌ Readability drops after adding more cases ❌ Easy to mix up arguments position ❌ Hard to scan or extend func add(_ a: Int, _ b: Int) -\u0026gt; Int { a + b } ... @Test(arguments: [ (a: 1, b: 2, result: 3), (10, 15, 25), (1, -5, -4), (-1, -5, -6), (0, 0, 0), (1000, 1000, 2000), (10000, 50000, 60000), (-10, -3, -13) ]) func add_returnsCorrectSum(a: Int, b: Int, result: Int) { #expect(add(a, b) == result) } Option 2: Named Tuples Every tuple entry explicitly names all its fields (a: \u0026hellip;, b: \u0026hellip;, result: \u0026hellip;) for all cases.\n✅ Clear - no guessing which value is which ✅ Easy to reorder or remove individual cases ❌ More repetitive boilerplate per line ❌ Becomes long and repetitive with many cases @Test(arguments: [ (a: 1, b: 2, result: 3), (a: 10, b: 15, result: 25), (a: 1, b: -5, result: -4), (a: -1, b: -5, result: -6), (a: 0, b: 0, result: 0), (a: 1000, b: 1000, result: 2000), (a: 10000, b: 50000, result: 60000), (a: -10, b: -3, result: -13) ]) func add_returnsCorrectSum(a: Int, b: Int, result: Int) { #expect(add(a, b) == result) } Option 3: Struct Use a TestCase struct and pass it to @Test\n✅ Perfect separation of data vs testing logic ✅ Great readability and maintainability ✅ Unlimited scalability ✅ IDE driven field suggestions ❌ More upfront work (struct definition) ❌ Might be overkill for 2-3 test cases struct TestCase { let a: Int let b: Int let result: Int static var all: [TestCase] { [ .init(a: 1, b: 2, result: 3), .init(a: 10, b: 15, result: 25), .init(a: 1, b: -5, result: -4), .init(a: -1, b: -5, result: -6), .init(a: 0, b: 0, result: 0), .init(a: 1000, b: 1000, result: 2000), .init(a: 10000, b: 50000, result: 60000), .init(a: -10, b: -3, result: -13) ] } } @Test(arguments: TestCase.all) func add_returnsCorrectSum(testCase: TestCase) { #expect(add(testCase.a, testCase.b) == testCase.result) } Options Comparison Criteria Option 1: First Named Tuple Option 2: Named Tuples Option 3: Struct Readability Poor (at scale) High Highest Scalability Poor Fair Excellent Boilerplate Low Medium Medium (upfront) Test arguments number 3 3 1 My final advice No One-Size-Fits-All\nNo single strategy works for every test suite. Evaluate pros and cons of each strategy and choose the one that best fit your needs.\nRemember - Tests are not just checks, they\u0026rsquo;re living documentation of your production code. Keep them super clear and don\u0026rsquo;t hesitate to ask your peers for feedback.\nPDF version ⤵️ 3 Ways to Name Parameters in Swift Parametrised Tests\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/3-ways-to-name-parameters-in-swift-parametrised-tests/","summary":"\u003cp\u003eMind your argument names in Swift Testing\u0026rsquo;s parametrised tests!\u003c/p\u003e\n\u003cp\u003eAs a follow up to my recent post on refactoring to use Swift Testing\u0026rsquo;s parametrised tests, I\u0026rsquo;m diving into the crucial - yet often overlooked topic of how to name your parametrised test inputs.\u003c/p\u003e\n\u003ch2 id=\"option-1-first-named-tuples\"\u003eOption 1: First Named Tuples\u003c/h2\u003e\n\u003cp\u003eOnly the first tuple is named, all others rely on positional matching to (a, b, result).\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e✅ Minimal boilerplate for small input sets\u003c/li\u003e\n\u003cli\u003e❌ Readability drops after adding more cases\u003c/li\u003e\n\u003cli\u003e❌ Easy to mix up arguments position\u003c/li\u003e\n\u003cli\u003e❌ Hard to scan or extend\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadd\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e a: Int, \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e b: Int) -\u0026gt; Int {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    a \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e b\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e@Test(arguments: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (a: \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, b: \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, result: \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e15\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e25\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e6\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (\u003cspan style=\"color:#ae81ff\"\u003e1000\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e1000\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2000\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (\u003cspan style=\"color:#ae81ff\"\u003e10000\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e50000\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e60000\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e13\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadd_returnsCorrectSum\u003c/span\u003e(a: Int, b: Int, result: Int) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    #expect(add(a, b) == result)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"option-2-named-tuples\"\u003eOption 2: Named Tuples\u003c/h2\u003e\n\u003cp\u003eEvery tuple entry explicitly names all its fields (a: \u0026hellip;, b: \u0026hellip;, result: \u0026hellip;) for all cases.\u003c/p\u003e","title":"3 Ways to Name Parameters in Swift Parametrised Tests"},{"content":"Intro Have you already started using Swift Testing instead of XCTest?\nI\u0026rsquo;m curious to see how you can refactor the test function (add_returnsCorrectSum) from the code snippet to use all powers of the Swift Testing framework.\nCould you explain what benefits does your refactored version has compared to my original code snippet?\nMy approach In both approaches the test gives the same result.\nThe point is the refactored version uses a Swift Testing parametrised test, and this makes a real difference.\nSo, what are the benefits of using parameterised tests over non-parameterised ones?\n1️⃣ Better test output reporting - in parametrised tests, each input is considered as separate test case and appears on it\u0026rsquo;s own line in the test report. This makes it easier to quickly spot which input caused a failure.\n2️⃣ Readability - the refactored test is reduced to a single tested function call. This shows that using parametrised tests we\u0026rsquo;re able to cut down on code duplication and make easier to understand the behaviour under test.\n3️⃣ Scalability - Adding a new test case is straightforward - you just add a new input and expected output. When the API of tested functionality changes, you only need to update it once.\nFinal Advice 🧐 When you find yourself repeating the same scenario with different inputs and expected outputs, always use parameterised tests. Try to look these patterns in your code and refactor using parametrised tests!\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/swift-testing-challange-can-you-refactor-this/","summary":"\u003ch2 id=\"intro\"\u003eIntro\u003c/h2\u003e\n\u003cp\u003eHave you already started using Swift Testing instead of XCTest?\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m curious to see how you can refactor the test function (\u003ccode\u003eadd_returnsCorrectSum\u003c/code\u003e) from the code snippet to use all powers of the Swift Testing framework.\u003c/p\u003e\n\u003cp\u003eCould you explain what benefits does your refactored version has compared to my original code snippet?\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"before_refactor\" loading=\"lazy\" src=\"/posts/swift-testing-challange-can-you-refactor-this/images/before_refactor.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"my-approach\"\u003eMy approach\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"after_refactor\" loading=\"lazy\" src=\"/posts/swift-testing-challange-can-you-refactor-this/images/after_refactor.png\"\u003e\u003c/p\u003e\n\u003cp\u003eIn both approaches the test gives the same result.\u003c/p\u003e\n\u003cp\u003eThe point is the refactored version uses a Swift Testing parametrised test, and this makes a real difference.\u003c/p\u003e","title":"Swift Testing Challange - Can you refactor this?"},{"content":"Swift code refactor in action 👨🏻‍💻\nTake a close look at the validate function. There\u0026rsquo;s a sneaky problem hidden in this code snippet.\nWhat will the function call return when passed nil? What problem is hidden here? First, the guard statement is redundant here. We can simplify the function to ⤵️\nfunc validate(password: String?) -\u0026gt; Bool { password?.count ?? 0 \u0026gt; 8 } There\u0026rsquo;s no need to wrap password?.count ?? 0 in parentheses since the ?? operator already has higher precedence than \u0026gt;.\nSecond, there\u0026rsquo;s a logical issue with the function signature. Does it make sense for the password to be optional? In my opinion, no. Logically, when validating a password, I\u0026rsquo;d assume the password already exists, and the argument should be non optional ⤵️\nfunc validate(password: String) -\u0026gt; Bool { password.count \u0026gt; 8 } Notice how clear the validation rule becomes now - it\u0026rsquo;s immediately understandable without any extra thinking.\nIf the text value is exposed by UI component as optional, we can conveniently handle this by introducing a simple extension ⤵️\nextension Optional where Wrapped == String { var orEmpty: String { self ?? \u0026#34;\u0026#34; } } Code snippet with full version ⤵️\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-code-refactor/3/","summary":"\u003cp\u003eSwift code refactor in action 👨🏻‍💻\u003c/p\u003e\n\u003cp\u003eTake a close look at the validate function. There\u0026rsquo;s a sneaky problem hidden in this code snippet.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWhat will the function call return when passed \u003ccode\u003enil\u003c/code\u003e?\u003c/li\u003e\n\u003cli\u003eWhat problem is hidden here?\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"initial_code_snippet\" loading=\"lazy\" src=\"/posts/series/swift-code-refactor/3/images/cover.png\"\u003e\u003c/p\u003e\n\u003cp\u003eFirst, the guard statement is redundant here. We can simplify the function to ⤵️\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003evalidate\u003c/span\u003e(password: String?) -\u0026gt; Bool {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    password?.count ?? \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThere\u0026rsquo;s no need to wrap password?.count ?? 0 in parentheses since the \u003ccode\u003e??\u003c/code\u003e operator already has higher precedence than \u003ccode\u003e\u0026gt;\u003c/code\u003e.\u003c/p\u003e","title":"#3 Swift code refactor in action - a sneaky problem hidden in code snippet"},{"content":"Paradigms Imperative In the imperative approach, we have a sequence of instructions that describe step by step how the program\u0026rsquo;s state is modified.\nLet\u0026rsquo;s look on the example ⤵️\nvar value = 0 func increment() { value += 1 // Mutating the state } print(value) // 0 increment() print(value) // 1 In the code we have a mutable variable value and a function increment that mutates the state (value). We first print the initial value, then increment it, and finally print the updated value.\nSimple, right? Let\u0026rsquo;s jump into functional programming!\nFunctional What is functional programming? Is it only about writing functions? 🤔\nNot really.\nOf course, functions are a foundation of functional programming, but they need to follow an important rule ⤵️\nIn functional programming, functions must not have side effects.\nWhat\u0026rsquo;s the side effect? - A side effect occurs when a function modifies any state outside of its own scope.\nLooking at the imperative programming example, we can clearly see a side effect inside the increment function ⤵️\nvar value = 0 func increment() { value += 1 // Mutating the state (Side effect) } print(value) // 0 increment() print(value) // 1 How can we re-shape this code to align with functional programming principles? - we need to get rid of state mutation both from main program flow and inside the increment function.\nLet\u0026rsquo;s fix the function first by passing the state (value) as an argument and returning the result of the operation as function\u0026rsquo;s output.\nvar value = 0 func increment(_ value: Int) -\u0026gt; Int { value + 1 } print(value) // 0 value = increment(value) print(value) // 1 Now, increment is a pure function. It returns a new state by adding 1 to the input without mutating any external state. To manage state changes in functional programming, we create new values instead of modifying the old state.\nHaving pure functions always returning the same output for a given input makes them easier to test and debug.\nNow we focus on mutability which functional programming aims to avoid. Instead of mutating the state - value, we transform the old state and return a new one.\nThe code refactored to the functional form ⤵️\nfunc increment(_ value: Int) -\u0026gt; Int { value + 1 } let initialState = 0 let incrementedState = increment(initialState) print(initialState) // 0 print(incrementedState) // 1 By adding the increment method as an extension to Int, we can use method chaining to apply multiple transformations in a clean, readable way. This is a common practice in functional programming, known as function or method composition ⤵️\nextension Int { func increment() -\u0026gt; Int { self + 1 } } let initialState = 0 let incrementedState = initialState .increment() .increment() print(initialState) // 0 print(incrementedState) // 2 Functional Reactive functional reactive programming is a paradigm that combines functional programming with publisher and subscriber pattern. It emphasises streams of events carrying data that can be transformed along the way to the subscriber. FRP enables better handling of concurrency and asynchronous operations, making code more expressive.\nIf we’d like to showcase the above example using FRP, it would look something like this ⤵️\nvar incrementSubject = PassthroughSubject\u0026lt;Void, Never\u0026gt;() var valueSubject = CurrentValueSubject\u0026lt;Int, Never\u0026gt;(0) let disposableStream = Publishers.CombineLatest(incrementSubject, valueSubject) .map { _, value in value + 1 } .subscribe(valueSubject) print(valueSubject.value) // 0 incrementSubject.send() print(valueSubject.value) // 1 Final Thoughts Having a deeper understanding of FRP, beyond just describing use cases like observing keyboard input to trigger requests, can truly make you stand out in an interview. The ability to explain the differences and principles behind Combine or RxSwift will position you as a more senior candidate in the eyes of the interviewer.\nQuiz --- primary_color: green secondary_color: lightgray text_color: black shuffle_questions: false shuffle_answers: true --- # What is functional programming about? 1. [ ] Writing functions that may modify state as needed 1. [x] Avoiding side effects and state mutation 1. [ ] Relying on side effects to manage state transitions 1. [ ] Emphasizing object-oriented design patterns # In functional programming, why must functions avoid side effects? 1. [ ] To improve performance 1. [x] To prevent unpredictable state changes 1. [ ] To enable state mutation 1. [ ] To allow recursion # Which paradigm describes a sequence of instructions that modify the program’s state step by step? 1. [ ] Functional 1. [ ] Functional Reactive 1. [x] Imperative 1. [ ] Declarative # What is considered a side effect in a function? 1. [ ] Returning a computed value 1. [x] Modifying a variable outside its scope 1. [ ] Calling another pure function 1. [ ] Using recursion # How is state typically managed in functional programming? 1. [ ] By mutating global variables 1. [x] By transforming old state into new state 1. [ ] By ignoring state changes 1. [ ] By centralizing state in a single object # Which pattern is used by functional reactive programming? 1. [ ] Singleton 1. [ ] Factory 1. [ ] Delegate and Callback 1. [x] Publisher and Subscriber # What is the primary benefit of using pure functions? 1. [ ] They allow state mutations 1. [x] They are easier to test and debug 1. [ ] They increase code complexity 1. [ ] They require global variables # Examine the code below. Does it fully follow the functional programming principles? ```swift var value = 0 func increment(_ value: Int) -\u0026gt; Int { value + 1 } value = increment(value) print(value) ``` 1. [ ] Yes, it\u0026#39;s correct 1. [ ] No, the function has side effects 1. [x] No, it mutates state by reassigning a mutable variable 1. [ ] No, it mutates the \u0026#34;value\u0026#34; variable inside the increment function Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/imperative-functional-frp/","summary":"\u003ch1 id=\"paradigms\"\u003eParadigms\u003c/h1\u003e\n\u003ch2 id=\"imperative\"\u003eImperative\u003c/h2\u003e\n\u003cp\u003eIn the imperative approach, we have a sequence of instructions that describe step by step how the program\u0026rsquo;s state is modified.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s look on the example ⤵️\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-swift\" data-lang=\"swift\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e value = \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eincrement\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    value \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// Mutating the state\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(value) \u003cspan style=\"color:#75715e\"\u003e// 0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eincrement()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(value) \u003cspan style=\"color:#75715e\"\u003e// 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the code we have a mutable variable \u003ccode\u003evalue\u003c/code\u003e and a function \u003ccode\u003eincrement\u003c/code\u003e that mutates the state (\u003ccode\u003evalue\u003c/code\u003e). We first print the initial value, then increment it, and finally print the updated value.\u003c/p\u003e","title":"Imperative, Functional, Functional Reactive: Do you know the difference?"},{"content":"Do You Know the Difference Between Imperative, Functional, and Reactive Programming?\nTest your understanding of imperative, functional, and functional reactive programming with this interactive quiz. These questions cover the key concepts from the main post to help you solidify your knowledge for your next interview.\n--- primary_color: green secondary_color: lightgray text_color: black shuffle_questions: false shuffle_answers: true --- # What is functional programming about? 1. [ ] Writing functions that may modify state as needed 1. [x] Avoiding side effects and state mutation 1. [ ] Relying on side effects to manage state transitions 1. [ ] Emphasizing object-oriented design patterns # In functional programming, why must functions avoid side effects? 1. [ ] To improve performance 1. [x] To prevent unpredictable state changes 1. [ ] To enable state mutation 1. [ ] To allow recursion # Which paradigm describes a sequence of instructions that modify the program’s state step by step? 1. [ ] Functional 1. [ ] Functional Reactive 1. [x] Imperative 1. [ ] Declarative # What is considered a side effect in a function? 1. [ ] Returning a computed value 1. [x] Modifying a variable outside its scope 1. [ ] Calling another pure function 1. [ ] Using recursion # How is state typically managed in functional programming? 1. [ ] By mutating global variables 1. [x] By transforming old state into new state 1. [ ] By ignoring state changes 1. [ ] By centralizing state in a single object # Which pattern is used by functional reactive programming? 1. [ ] Singleton 1. [ ] Factory 1. [ ] Delegate and Callback 1. [x] Publisher and Subscriber # What is the primary benefit of using pure functions? 1. [ ] They allow state mutations 1. [x] They are easier to test and debug 1. [ ] They increase code complexity 1. [ ] They require global variables # Examine the code below. Does it fully follow the functional programming principles? ```swift var value = 0 func increment(_ value: Int) -\u0026gt; Int { value + 1 } value = increment(value) print(value) ``` 1. [ ] Yes, it\u0026#39;s correct 1. [ ] No, the function has side effects 1. [x] No, it mutates state by reassigning a mutable variable 1. [ ] No, it mutates the \u0026#34;value\u0026#34; variable inside the increment function Didn\u0026rsquo;t get the full result? No worries! All the answers are in the blog post HERE\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/quizzes/imperative_functional_frp/","summary":"\u003cp\u003eDo You Know the Difference Between Imperative, Functional, and Reactive Programming?\u003c/p\u003e\n\u003cp\u003eTest your understanding of imperative, functional, and functional reactive programming with this interactive quiz. These questions cover the key concepts from the main post to help you solidify your knowledge for your next interview.\u003c/p\u003e\n\u003cdiv class='quizdown'\u003e\n  \n\n---\nprimary_color: green\nsecondary_color: lightgray\ntext_color: black\nshuffle_questions: false\nshuffle_answers: true\n---\n\n# What is functional programming about?\n\n1. [ ] Writing functions that may modify state as needed\n1. [x] Avoiding side effects and state mutation\n1. [ ] Relying on side effects to manage state transitions\n1. [ ] Emphasizing object-oriented design patterns\n\n# In functional programming, why must functions avoid side effects?\n\n1. [ ] To improve performance\n1. [x] To prevent unpredictable state changes\n1. [ ] To enable state mutation\n1. [ ] To allow recursion\n\n# Which paradigm describes a sequence of instructions that modify the program’s state step by step?\n\n1. [ ] Functional\n1. [ ] Functional Reactive\n1. [x] Imperative\n1. [ ] Declarative\n\n# What is considered a side effect in a function?\n\n1. [ ] Returning a computed value\n1. [x] Modifying a variable outside its scope\n1. [ ] Calling another pure function\n1. [ ] Using recursion\n\n# How is state typically managed in functional programming?\n\n1. [ ] By mutating global variables\n1. [x] By transforming old state into new state\n1. [ ] By ignoring state changes\n1. [ ] By centralizing state in a single object\n\n# Which pattern is used by functional reactive programming?\n\n1. [ ] Singleton\n1. [ ] Factory\n1. [ ] Delegate and Callback\n1. [x] Publisher and Subscriber\n\n# What is the primary benefit of using pure functions?\n\n1. [ ] They allow state mutations\n1. [x] They are easier to test and debug\n1. [ ] They increase code complexity\n1. [ ] They require global variables\n\n# Examine the code below. Does it fully follow the functional programming principles?\n\n```swift\nvar value = 0\n\nfunc increment(_ value: Int) -\u0026gt; Int {\n    value + 1\n}\n\nvalue = increment(value)\nprint(value)\n```\n1. [ ] Yes, it\u0026#39;s correct\n1. [ ] No, the function has side effects\n1. [x] No, it mutates state by reassigning a mutable variable\n1. [ ] No, it mutates the \u0026#34;value\u0026#34; variable inside the increment function\n\n\n\u003c/div\u003e\n\n\u003cp\u003eDidn\u0026rsquo;t get the full result? No worries! All the answers are in the blog post \u003ca href=\"https://www.mobiledevdiary.com/posts/imperative-functional-frp/\"\u003eHERE\u003c/a\u003e\u003c/p\u003e","title":"Imperative, Functional, Functional Reactive"},{"content":"What’s the difference? In XCTest we relied on the old fashioned simple comments to add more context to our test case e.g link to the bug description.\nWith Swift Testing, we now have a special bug trait that can be passed to the @Test macro. The bug trait takes a URL String as argument and optionally a title for the bug allowing us to add short description of it. The key advantage over regular comment is that the bug title is visible in the test results. Better yet, tapping on it takes you directly to the related webpage with the bug report.\nMy thoughts I\u0026rsquo;m happy to see a consistent way to link tests with bugs in a bug tracking system. To be honest, I rarely comment on my tests - I believe a test name should be self-descriptive. I see the potential for this feature when doing bug fixing in TDD. You can start by writing a test and adding a link to the bug in a bug tracking system. Later, when the bug is fixed, the link can serve as a proof that the particular bug is covered by automated tests. I highly recommend the approach of fixing bugs in TDD!\nI struggled a bit with finding where I can see the link to issue tracker in tests results (check out the last GIF). However, I’m optimistic about the potential of this feature and excited to see how it evolves in the future!\nExamples Code ⤵️\nXCTest\n/* \u0026#34;This test is addressing a bug where incorrect thread handling led to unexpected crash\u0026#34; Check: https://www.mobiledevdiary.com/issues/12345 */ func testPerformFetchFromNonMainThreadDoesNotCrash() { // Test body } Swift Testing\n@Test(.bug( \u0026#34;https://www.mobiledevdiary.com/issues/12345\u0026#34;, \u0026#34;This test is addressing a bug where incorrect thread handling led to unexpected crash\u0026#34; )) func testPerformFetchFromNonMainThreadDoesNotCrash() { // Test body } Location of the bug button ⤵️\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-testing-vs-xctest/7-bugs-linking/","summary":"\u003ch3 id=\"whats-the-difference\"\u003eWhat’s the difference?\u003c/h3\u003e\n\u003cp\u003eIn XCTest we relied on the old fashioned simple comments to add more context to our test case e.g link to the bug description.\u003c/p\u003e\n\u003cp\u003eWith Swift Testing, we now have a special \u003ccode\u003ebug\u003c/code\u003e trait that can be passed to the \u003ccode\u003e@Test\u003c/code\u003e macro. The \u003ccode\u003ebug\u003c/code\u003e trait takes a URL String as argument and optionally a title for the bug allowing us to add short description of it. The key advantage over regular comment is that the bug title is visible in the test results. Better yet, tapping on it takes you directly to the related webpage with the bug report.\u003c/p\u003e","title":"#7 XCTest vs Swift Testing: A modern way of linking bugs"},{"content":"What’s the difference? XCTest doesn\u0026rsquo;t provide a built-in solution for parameterized tests. To achieve this, we create test cases as structs or tuples, defining test inputs and expected results. Then, we write a loop to iterate through these test cases and execute the necessary assertions.\nSwift Testing simplifies this process by allowing tests to be parameterized directly. Using the @Test macro, you can pass test cases as an argument. What\u0026rsquo;s the benefit? While you still need to define your test cases, the iteration code is no longer needed — Swift Testing handles it for you.\nMy thoughts I\u0026rsquo;m obviously glad to see the code simplification! As a software developer, I enjoy writing less code to achieve the same results. Looking at it with a skeptical eye — it\u0026rsquo;s not a groundbreaking change that will revolutionize how we write tests. However, it\u0026rsquo;s a satisfying improvement that may encourage developers to rethink how they write tests and potentially adopt this pattern where it makes sense.\nExamples Code ⤵️\nXCTest\nstatic let testCases = [ (text: \u0026#34;ABC\u0026#34;, result: false), (text: \u0026#34;🚀\u0026#34;, result: true), (text: \u0026#34;ABC 👾\u0026#34;, result: true) ] func testTextContainsEmoji() { Self.testCases.forEach { test in XCTAssertEqual(test.text.containsEmoji, test.result) } } Swift Testing\nstatic let testCases = [ (text: \u0026#34;ABC\u0026#34;, result: false), (text: \u0026#34;🚀\u0026#34;, result: true), (text: \u0026#34;ABC 👾\u0026#34;, result: true) ] @Test(arguments: testCases) func textContainsEmoji(testCase: (text: String, result: Bool)) { #expect(testCase.text.containsEmoji == testCase.result) } Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-testing-vs-xctest/6-parametrized-tests/","summary":"\u003ch3 id=\"whats-the-difference\"\u003eWhat’s the difference?\u003c/h3\u003e\n\u003cp\u003eXCTest doesn\u0026rsquo;t provide a built-in solution for parameterized tests. To achieve this, we create test cases as structs or tuples, defining test inputs and expected results. Then, we write a loop to iterate through these test cases and execute the necessary assertions.\u003c/p\u003e\n\u003cp\u003eSwift Testing simplifies this process by allowing tests to be parameterized directly. Using the \u003ccode\u003e@Test\u003c/code\u003e macro, you can pass test cases as an argument. What\u0026rsquo;s the benefit? While you still need to define your test cases, the iteration code is no longer needed — Swift Testing handles it for you.\u003c/p\u003e","title":"#6 XCTest vs Swift Testing - Parameterized tests in the fight for more reusable code"},{"content":"Swift code refactor in action 👨🏻‍💻\nToday, let’s talk about refactoring for clarity, maintainability, and scalability!\nThis time, the scenario is calculating the final price depending on the price and membership status.\nThis initial code has a few code smells:\n1️⃣ nested ifs - impacts general readability, making the code hard to understand, 2️⃣ duplicated conditions - \u0026ldquo;price \u0026gt; 100\u0026rdquo; which violates the Don’t Repeat Yourself principle, 3️⃣ lack of scalability - not possible to easily add a new discount, it’d require to rework everything. Checkout the gif or the post on my website to see how I solve these code smells and make the code cool, clean and scalable!\nGif ⤵️\nCode ⤵️\n#1\nfunc calculatePrice_v1(price: Double, isMember: Bool) -\u0026gt; Double { if isMember { if price \u0026gt; 100 { return price * 0.9 } else { return price * 0.95 } } else { if price \u0026gt; 100 { return price * 0.95 } else { return price } } } #2\nfunc calculatePrice_v2(price: Double, isMember: Bool) -\u0026gt; Double { isMember ? priceForMember(price: price) : priceForNonMember(price: price) } func priceForMember(price: Double) -\u0026gt; Double { price \u0026gt; 100 ? price * 0.9 : price * 0.95 } func priceForNonMember(price: Double) -\u0026gt; Double { price \u0026gt; 100 ? price * 0.95 : price } #3\nfunc calculatePrice_v3(price: Double, isMember: Bool) -\u0026gt; Double { apply(discount: isMember ? .member : .regular, to: price) } func apply(discount: Discount, to price: Double) -\u0026gt; Double { price \u0026gt; discount.threshold ? price * discount.premiumRate : price * discount.standardRate } struct Discount { let threshold: Double let standardRate: Double let premiumRate: Double static let member = Discount(threshold: 100, standardRate: 0.95, premiumRate: 0.9) static let regular = Discount(threshold: 100, standardRate: 1, premiumRate: 0.95) } Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-code-refactor/2/","summary":"\u003cp\u003eSwift code refactor in action 👨🏻‍💻\u003c/p\u003e\n\u003cp\u003eToday, let’s talk about refactoring for clarity, maintainability, and scalability!\u003c/p\u003e\n\u003cp\u003eThis time, the scenario is calculating the final price depending on the price and membership status.\u003c/p\u003e\n\u003cp\u003eThis initial code has a few code smells:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e1️⃣ nested ifs - impacts general readability, making the code hard to understand,\u003c/li\u003e\n\u003cli\u003e2️⃣ duplicated conditions - \u0026ldquo;price \u0026gt; 100\u0026rdquo; which violates the Don’t Repeat Yourself principle,\u003c/li\u003e\n\u003cli\u003e3️⃣ lack of scalability - not possible to easily add a new discount, it’d require to rework everything.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eCheckout the gif or the post on my website to see how I solve these code smells and make the code cool, clean and scalable!\u003c/p\u003e","title":"#2 Swift code refactor in action - price $$$"},{"content":"Today we check the diff in conditional disabling.\nIn XCTest there is XCTSkipIf function that takes Bool argument to decide whether a test should run or not. In Swift Testing there’s \u0026ldquo;disable\u0026rdquo; trait accepting Bool argument and behaving like XCTSkipIf from XCTest. XCTSkipIf - are you surprised this kind of function exists? To be honest - I was\nI can admit I learned about it when preparing this post. This already shows how often I’ll be using the Swift Testing version of it, but never say never!\nAnyway, Swift Testing lets you move the disabling logic outside of the test body, giving it a cleaner, more intuitive syntax. For me, that’s a win over XCTest.\nCode ⤵️\nXCTest\nenum FeatureFlag { static let isExampleTestDisabled = true } func testExampleTest() throws { try XCTSkipIf(FeatureFlag.isExampleTestDisabled) XCTAssertEqual(1 + 1, 3) } Swift Testing\nenum FeatureFlag { static let isExampleTestDisabled = true } @Test(.disabled(if: FeatureFlag.isExampleTestDisabled)) func exampleTest() throws { #expect(1 + 1 == 3) } Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-testing-vs-xctest/5-conditional-disabling/","summary":"\u003cp\u003eToday we check the diff in conditional disabling.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIn XCTest there is \u003ccode\u003eXCTSkipIf\u003c/code\u003e function that takes \u003ccode\u003eBool\u003c/code\u003e argument to decide whether a test should run or not.\u003c/li\u003e\n\u003cli\u003eIn Swift Testing there’s \u0026ldquo;disable\u0026rdquo; trait accepting \u003ccode\u003eBool\u003c/code\u003e argument and behaving like \u003ccode\u003eXCTSkipIf\u003c/code\u003e from XCTest.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eXCTSkipIf - are you surprised this kind of function exists? To be honest - I was\u003c/p\u003e\n\u003cp\u003eI can admit I learned about it when preparing this post. This already shows how often I’ll be using the Swift Testing version of it, but never say never!\u003c/p\u003e","title":"#5 XCTest vs. Swift Testing - Conditional disabling - when a test needs a nap"},{"content":"This week with Swift Testing starts with checking how test disabling differs from XCTest.\nIn XCTest, Xcode identifies a function as a test only if its name starts with the \u0026ldquo;test\u0026rdquo; prefix, so putting e.g. \u0026ldquo;disabled\u0026rdquo; instead makes the test inactive. Swift Testing simplifies that approach by introducing the @Test macro with a .disabled trait that you can pass as an argument.\nWhat’s the benefit? You no longer need to modify each test name to disable it. What’s more, you can include context directly within the trait to justify why the test is disabled.\nDo I like it? You bet! I’m really glad that Xcode no longer depends on a \u0026ldquo;test\u0026rdquo; or other prefix 🥳\nNa matter what’s the motive, your goal should always be to resolve it, because a disabled test does not provide any value to the project.\nCode ⤵️\nXCTest\n// Disabled due to bug #12345 func disabled_testExampleTest() { XCTAssertEqual(1 + 1, 3) } Swift Testing\n@Test(.disabled(\u0026#34;Disabled due to bug #12345\u0026#34;)) func exampleTest() { #expect(1 + 1 == 3) } Why is \u0026ldquo;handle with care\u0026rdquo; mentioned in the title? It’s simple - disabling a test it’s a rare situation and must always come with a good reason.\nRandomly failing test blocks a team from progressing - valid reason. Changes in the production code without updating tests - that should never happen. Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-testing-vs-xctest/4-disable-tests/","summary":"\u003cp\u003eThis week with Swift Testing starts with checking how test disabling differs from XCTest.\u003c/p\u003e\n\u003cp\u003eIn XCTest, Xcode identifies a function as a test only if its name starts with the \u0026ldquo;test\u0026rdquo; prefix, so putting e.g. \u0026ldquo;disabled\u0026rdquo; instead makes the test inactive.\nSwift Testing simplifies that approach by introducing the @Test macro with a \u003ccode\u003e.disabled\u003c/code\u003e trait that you can pass as an argument.\u003c/p\u003e\n\u003cp\u003eWhat’s the benefit?\nYou no longer need to modify each test name to disable it. What’s more, you can include context directly within the trait to justify why the test is disabled.\u003c/p\u003e","title":"#4 XCTest vs. Swift Testing - Disable tests - handle with care"},{"content":"What is macro? 💡 Macro is a feature that generates code during compilation. Unlike macros in C, which work like “find and replace”, Swift macros are type-safe and context aware, making them powerful tools reducing boilerplate code.\nTwo types of macros\nattached - use @ prefix, tied to a declaration adding extra logic to it, like: @Test, @Model, @Observable freestanding - use # prefix, standalone code that can be invoked independently as a part of the code, like #expect, #Predicate, #warning Example of attached macro ⤵️\n@Test func addition() { // tied to the declaration ... } Example of freestanding macro ⤵️\n@Test func addition() { #require(1 + 2 == 3) // not attached to a declaration } Bonus It’s possible to expand macros, especially those defined by you, and check their implementation by right-clicking on a macro and selecting the \u0026ldquo;Expand Macro\u0026rdquo; option. ⤵️\nResources Swift Docs Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/two-types-of-swift-macros/","summary":"\u003ch3 id=\"what-is-macro\"\u003eWhat is macro?\u003c/h3\u003e\n\u003cp\u003e💡 Macro is a feature that generates code during compilation. Unlike macros in C, which work like “find and replace”, Swift macros are type-safe and context aware, making them powerful tools reducing boilerplate code.\u003c/p\u003e\n\u003cp\u003eTwo types of macros\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eattached - use \u003ccode\u003e@\u003c/code\u003e prefix, tied to a declaration adding extra logic to it, like: \u003ccode\u003e@Test\u003c/code\u003e, \u003ccode\u003e@Model\u003c/code\u003e, \u003ccode\u003e@Observable\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003efreestanding - use \u003ccode\u003e#\u003c/code\u003e prefix, standalone code that can be invoked independently as a part of the code, like \u003ccode\u003e#expect\u003c/code\u003e, \u003ccode\u003e#Predicate\u003c/code\u003e, \u003ccode\u003e#warning\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eExample of attached macro ⤵️\u003c/p\u003e","title":"Two types of Swift macros"},{"content":"Optionals are a core of Swift - we deal with them daily, both in production and testing code. Whether you write tests with XCTest or Swift Testing, unwrapping optionals is a common case.\nIn XCTest there is XCTUnwrap operator.\nSwift Testing introduces #require macro.\nIs there any difference between them? Not really! Both require the test function to handle exceptions and try keyword before.\nGif ⤵️\nCode ⤵️\nXCTest\nfunc testNewYorkTimeZone() throws { let newYorkTimeZone = try XCTUnwrap( TimeZone(identifier: \u0026#34;America/New_York\u0026#34;) ) XCTAssertEqual(newYorkTimeZone.abbreviation(), \u0026#34;GMT-5\u0026#34;) XCTAssertEqual(newYorkTimeZone.identifier, \u0026#34;America/New_York\u0026#34;) } Swift Testing\n@Test func newYorkTimeZone() throws { let newYorkTimeZone = try #require( TimeZone(identifier: \u0026#34;America/New_York\u0026#34;) ) #expect(newYorkTimeZone.abbreviation() == \u0026#34;GMT-5\u0026#34;) #expect(newYorkTimeZone.identifier == \u0026#34;America/New_York\u0026#34;) } Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-testing-vs-xctest/3-unwrapping-optionals/","summary":"\u003cp\u003eOptionals are a core of Swift - we deal with them daily, both in production and testing code. Whether you write tests with XCTest or Swift Testing, unwrapping optionals is a common case.\u003c/p\u003e\n\u003cp\u003eIn XCTest there is \u003ccode\u003eXCTUnwrap\u003c/code\u003e operator.\u003c/p\u003e\n\u003cp\u003eSwift Testing introduces \u003ccode\u003e#require\u003c/code\u003e macro.\u003c/p\u003e\n\u003cp\u003eIs there any difference between them? Not really! Both require the test function to handle exceptions and \u003ccode\u003etry\u003c/code\u003e keyword before.\u003c/p\u003e\n\u003cp\u003eGif ⤵️\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Example\" loading=\"lazy\" src=\"/posts/series/swift-testing-vs-xctest/3-unwrapping-optionals/images/example.gif\"\u003e\u003c/p\u003e\n\u003cp\u003eCode ⤵️\u003c/p\u003e\n\u003cp\u003eXCTest\u003c/p\u003e","title":"#3 XCTest vs. Swift Testing - Unwrapping optionals"},{"content":"Swift code refactor in action 👨🏻‍💻\nCommon scenario: formatting user profile name - I bet any of you faced this kind of task.\nAt first glance, it look straightforward, but when you take a closer look, you’ll notice two potential improvements:\n1️⃣ One single return - simplification of the function flow.\n2️⃣ Centralised formatting logic - reduces the chance of bugs.\nCheck out the animated gif and the code where I refactor to address these issues.\nAdvice: The easiest and fastest way to validate your refactor is to have the code unit tested, so remember to test it before you start refactoring!\nGif ⤵️\nCode ⤵️\n#1\n/// Returns the user\u0026#39;s full name, optionally including a nickname in the format: /// - With nickname: `[Johnny] John Doe` /// - Without nickname: `John Doe` func userProfileName(firstName: String, lastName: String, nickname: String?) -\u0026gt; String { if let nickname { \u0026#34;[\\(nickname)] \\(firstName) \\(lastName)\u0026#34; } else { \u0026#34;\\(firstName) \\(lastName)\u0026#34; } } #2\n/// Returns the user\u0026#39;s full name, optionally including a nickname in the format: /// - With nickname: `[Johnny] John Doe` /// - Without nickname: `John Doe` func userProfileName(firstName: String, lastName: String, nickname: String?) -\u0026gt; String { nickname.map { nickname in [\u0026#34;[\\(nickname)]\u0026#34;, firstName, lastName].joined(separator: \u0026#34; \u0026#34;) } ?? [firstName, lastName].joined(separator: \u0026#34; \u0026#34;) } #3\n/// Returns the user\u0026#39;s full name, optionally including a nickname in the format: /// - With nickname: `[Johnny] John Doe` /// - Without nickname: `John Doe` func userProfileName(firstName: String, lastName: String, nickname: String?) -\u0026gt; String { [nickname.map { nickname in \u0026#34;[\\(nickname)]\u0026#34; }, firstName, lastName] .compactMap { $0 } .joined(separator: \u0026#34; \u0026#34;) } Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-code-refactor/1/","summary":"\u003cp\u003eSwift code refactor in action 👨🏻‍💻\u003c/p\u003e\n\u003cp\u003eCommon scenario: formatting user profile name - I bet any of you faced this kind of task.\u003c/p\u003e\n\u003cp\u003eAt first glance, it look straightforward, but when you take a closer look, you’ll notice two potential improvements:\u003c/p\u003e\n\u003cp\u003e1️⃣ One single return - simplification of the function flow.\u003c/p\u003e\n\u003cp\u003e2️⃣ Centralised formatting logic - reduces the chance of bugs.\u003c/p\u003e\n\u003cp\u003eCheck out the animated gif and the code where I refactor to address these issues.\u003c/p\u003e","title":"#1 Swift code refactor in action - user profile name"},{"content":"Today we check how testing error has changed in the new framework.\nIn XCTest, we use XCTAssertThrowsError to check if a specific error is thrown. This assertion comes with the error handler closure where we can perform additional checks like e.g. verifying the exact error type.\nWith Swift Testing, this process is even simpler, especially when an error conforms to Equatable. We can directly specify the expected error in the expect macro and it automatically checks the type.\nTo me, this change makes the test simpler and easier to follow. In the end we have less lines to write and read, right? 😅\nGif ⤵️\nCode ⤵️\nXCTest\nfunc testExampleError() { enum CustomError: Error, Equatable { case fatal case warning } func throwError() throws { throw CustomError.fatal } XCTAssertThrowsError(try throwError()) { error in XCTAssertEqual(error as? CustomError, .fatal) } } Swift Testing\n@Test func exampleError() { enum CustomError: Error, Equatable { case fatal case warning } func throwError() throws { throw CustomError.fatal } #expect(throws: CustomError.fatal, performing: throwError) } Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-testing-vs-xctest/2-has-error-testing-been-simplified/","summary":"\u003cp\u003eToday we check how testing error has changed in the new framework.\u003c/p\u003e\n\u003cp\u003eIn XCTest, we use \u003ccode\u003eXCTAssertThrowsError\u003c/code\u003e to check if a specific error is thrown. This assertion comes with the error handler closure where we can perform additional checks like e.g. verifying the exact error type.\u003c/p\u003e\n\u003cp\u003eWith Swift Testing, this process is even simpler, especially when an error conforms to \u003ccode\u003eEquatable\u003c/code\u003e. We can directly specify the expected error in the expect macro and it automatically checks the type.\u003c/p\u003e","title":"#2 XCTest vs. Swift Testing - Has error testing been simplified?"},{"content":"New Series! XCTest vs. Swift Testing - fresh look on a new testing framework.\nSwift Testing was presented at WWDC24 as a new, modern, simplified framework for writing automated tests. It\u0026rsquo;s a perfect candidate to replace XCTest unit tests, so it\u0026rsquo;s definitely worth learning.\nI haven’t had a chance yet to use Swift Testing in production and the series is my motivation for me to discover it.\nToday we cover 2 basic differences ⤵️\n1️⃣ No more \u0026ldquo;test\u0026rdquo; prefix in test names. In XCTest each test name has to start with the \u0026ldquo;test\u0026rdquo; prefix. In Swift Testing Xcode recognise a test by a new macro \u0026ldquo;@Test\u0026rdquo;.\n2️⃣ Unified way of checking test results with #expect In XCTest there are assertions: XCTAssertEqual, XCTAssertTrue, XCTAssertNil, … Swift Testing simplifies that with a single macro #expect. So now XCTAssertTrue(1 + 1, 2), becomes #expect(1 + 1 == 2).\nI’m definitely a fan of the @Test macro as this small change makes the testing code cleaner. However I’m not 100% sure about replacing all assertions with one macro. What makes me think it was a good idea - is a simplification and making on single verification function instead of several ones. Maybe more experience with Swift Testing will convince me to have a stronger opinion about it… ?\nGif ⤵️\nCode ⤵️\nXCTest\nfunc testConversionOfXCTestAssertionsToSwiftTestingMacros() { XCTAssertEqual(5 + 5, 10) XCTAssertTrue(5 + 5 == 10) XCTAssertFalse(5 + 2 == 10) XCTAssertGreaterThan(10, 5) XCTAssertGreaterThanOrEqual(5, 5) XCTAssertNil(Int(\u0026#34;Not Int\u0026#34;)) } Swift Testing\n@Test func conversionOfXCTestAssertionsToSwiftTestingMacros() { #expect(5 + 5 == 10) #expect(5 + 5 == 10) #expect(5 + 2 != 10) #expect(10 \u0026gt; 5) #expect(5 \u0026gt;= 5) #expect(Int(\u0026#34;Not Int\u0026#34;) == nil) } Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/swift-testing-vs-xctest/1-fresh-look-on-a-new-testing-framework/","summary":"\u003cp\u003eNew Series! XCTest vs. Swift Testing - fresh look on a new testing framework.\u003c/p\u003e\n\u003cp\u003eSwift Testing was presented at WWDC24 as a new, modern, simplified framework for writing automated tests. It\u0026rsquo;s a perfect candidate to replace XCTest unit tests, so it\u0026rsquo;s definitely worth learning.\u003c/p\u003e\n\u003cp\u003eI haven’t had a chance yet to use Swift Testing in production and the series is my motivation for me to discover it.\u003c/p\u003e\n\u003cp\u003eToday we cover 2 basic differences ⤵️\u003c/p\u003e","title":"#1 XCTest vs. Swift Testing - fresh look on a new testing framework"},{"content":"Recap Hello everyone and welcome to the next chapter of the series about SwiftUI code automated testing!\nIn the previous post we\ndefined acceptance criteria for the Joke app that we\u0026rsquo;re implementing we covered by snapshot tests all UI cases mentioned in the acceptance critieria That\u0026rsquo;s what the app looks like ⤵️\nHere\u0026rsquo;s the link to the previous blog post ⤵️ (Worth reading before this one)\nTesting SwiftUI Code - The beginning (UI)\nChapter III - Triggering API request Intro The goal of that chapter is to start implementing business logic described in the acceptanca critieria here.\nWe\u0026rsquo;re going to focus on the point ⤵️\nAs a user I can get a new joke\nwhen you tap the button “Tell me another!”, the API request fetching a new random joke is triggered. We\u0026rsquo;ll be adding unit tests based on the acceptance criteria and the plan is to avoid mocking wherever it\u0026rsquo;s possible. In theory it\u0026rsquo;s simple, but in reallity it\u0026rsquo;s pretty complex and it requires introducing a few tricks.\nWhen implementing we\u0026rsquo;ll keep in mind good unit tests attributes ⤵️\nfast 🏎️ - a project usually consists of hundreds / thousands unit tests, so they have to be really quick to give feedback fast to a developer. If tests are slow, no one wants to run them and the cost of maintanace and execution might be overwhelming. deterministic 🎯 - for the given input there is always the same output, no matter the environment, time or order of execution isolated 🚪 - avoid using shared states (like Singletons) that can impact a test result Test draft, first challange As a reminder, here is the request structure ⤵️\nGET https://official-joke-api.appspot.com/random_joke { \u0026#34;type\u0026#34;: \u0026#34;programming\u0026#34;, \u0026#34;setup\u0026#34;: \u0026#34;The punchline often arrives before the set-up.\u0026#34;, \u0026#34;punchline\u0026#34;: \u0026#34;Do you know the problem with UDP jokes?\u0026#34;, \u0026#34;id\u0026#34;: 73 } First things first! Let\u0026rsquo;s focus on checking if the correct endpoint is called. We start with the basic test: when you tap the button “Tell me another!”, the API request is called. Then we build on the top of it.\nLet\u0026rsquo;s create the JokeViewTests file and class and then configure the sut (system under test - JokeView) inside ⤵️\nclass JokeViewTests: XCTestCase { func test_whenTappingOnTheButton_ItTriggersRequest() { let sut = JokeView(state: .loaded(joke: .udp)) } } private extension Joke { static var udp: Joke { .init( setup: \u0026#34;The punchline often arrives before the set-up.\u0026#34;, punchline: \u0026#34;Do you know the problem with UDP jokes?\u0026#34; ) } } We\u0026rsquo;ve hardly started and already faced a first challnage on our way! - How to execute button action in the test? 🤔\nSpoiler: You can try to find APIs built into standard frameworks, but you won\u0026rsquo;t succeed 😢\nLuckily 🍀, we have a great community that comes to the rescue! ⤵️\nTriggering the button action using ViewInspector There is a way of triggering the button action using ViewInspector library.\nViewInspector is a library for unit testing SwiftUI views. It allows for traversing a view hierarchy at runtime providing direct access to the underlying View structs.\nIt comes with SPM support, so let\u0026rsquo;s add it to our project. Make sure to add the package to the testing target ‼️\nTo check whether the ViewInspector works, we\u0026rsquo;ll now update the test. Let\u0026rsquo;s assume we want to inject the closure () -\u0026gt; Void to the JokeView and then trigger it after the \u0026ldquo;Tell me another!\u0026rdquo; button is tapped. That test confirms that ViewInspector is able to trigger the button action. Let\u0026rsquo;s start with updating the test ⤵️\nfunc test_whenTappingOnTheButton_ItTriggersRequest() throws { var isActionTriggered = false let sut = JokeView(state: .loaded(joke: .udp)) { isActionTriggered = true } try sut.inspect().find(button: \u0026#34;Tell me another!\u0026#34;).tap() // 1 XCTAssertTrue(isActionTriggered) } // 1 - To find the exact button we use the function find from the ViewInspector that looks for the button with given text in the View structure.\nfunc find(text: String, locale: Locale = .testsDefault) throws -\u0026gt; InspectableView\u0026lt;ViewType.Text\u0026gt; It should not compile, so we are in the red stage 🔴. Next step - add missing implementation ⤵️\nstruct JokeView: View { ... private let buttonAction: @escaping () -\u0026gt; Void init(state: JokeState, buttonAction: @escaping () -\u0026gt; Void) { ... self.buttonAction = buttonAction } var body: some View { Button(action: { buttonAction() }) { Text(\u0026#34;Tell me another!\u0026#34;) ... } ... } } } Now we run the test and check if it\u0026rsquo;s 🟢 ⏳ \u0026hellip;\nAfter the moment the test should pass ✅, so we get confirmation that ViewInspector library fits our needs 🤗\nAPI request execution We can revert the change with buttonAction now - remove () -\u0026gt; Void closure passed to the JokeView, update test to compile and go back to our flow to test.\nTo communicate with API we use native URLSession more precisely - the function that uses async / await ⤵️\npublic func data(for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -\u0026gt; (Data, URLResponse) It was said that good unit tests should be fast, deterministic and isolated, so they cannot be based on the real API. That means we cannot use the real API in the test, so we need to mock it somehow.\nInside the test we need the request executor closure that will have the same signature like data function from URLSession ⤵️\nfunc test_whenTappingOnTheButton_ItTriggersRequest() throws { var requestExecutorInvokedWithRequest: URLRequest? // 2 let sut = JokeView(state: .loaded(joke: .udp)) { request, _ in // 1 requestExecutorInvokedWithRequest = request // 2 return (Data(), URLResponse()) // 3 } try sut.inspect().find(button: \u0026#34;Tell me another!\u0026#34;).tap() XCTAssertEqual( requestExecutorInvokedWithRequest?.url, .init(URL(string: \u0026#34;https://official-joke-api.appspot.com/random_joke\u0026#34;)!) ) // 4 } // 1 - JokeView takes (URLRequest, (any URLSessionTaskDelegate)?) async throws -\u0026gt; (Data, URLResponse) closure as an argument. // 2 - When the closure is called we can intercept the request, so that we can check the URL called. // 3 - To satisfy the compiler we return stubbed data (We\u0026rsquo;ll be covering that part later). // 4 - We assert the URL intercepted from the request executor. When we run the test we find out it does not compile. It\u0026rsquo;s expected, because we assume JokeView takes additional argument. Nevertheless, we are in the red stage 🔴, so we can start updating the implementation ⤵️\nstruct JokeView: View { private let requestExecutor: (URLRequest, (any URLSessionTaskDelegate)?) async throws -\u0026gt; (Data, URLResponse) @State var state: JokeState init( state: JokeState, requestExecutor: @escaping (URLRequest, (any URLSessionTaskDelegate)?) async throws -\u0026gt; (Data, URLResponse) = URLSession.shared.data ) { self.state = state self.requestExecutor = requestExecutor } ... } After this change the code compiles again, but the test fails. To make it green 🟢 we need to add the code requesting a new joke ⤵️\n... var body: some View { VStack { ... Button(action: { fetchNewJoke() }) ... } } func fetchNewJoke() { let url = URL( string: \u0026#34;https://official-joke-api.appspot.com/random_joke\u0026#34; ).unsafelyUnwrapped let request = URLRequest(url: url) Task { // 1 try! await requestExecutor(request, nil) } } ... // 1 - RequestExecutor closure is async, so it has to be wrapped in Task. We run the test again: did it pass? Yes and no. By wrapping the code inside the Task we introduced the asynchronicity to the production code that makes the test not work in a deterministic way - it randomly fails and passes.\nThe problem of asynchronism in the test To resolve the problem of the code part running asynchornously, we use XCTestExpectation inside the test ⤵️\nfunc test_whenTappingOnTheButton_ItTriggersRequest() throws { var requestExecutorInvokedWithRequest: URLRequest? let expectation = XCTestExpectation(description: \u0026#34;Wait for request being executed\u0026#34;) // 1 let sut = JokeView(state: .loaded(joke: .udp)) { request, _ in requestExecutorInvokedWithRequest = request expectation.fulfill() // 3 return (Data(), URLResponse()) } try sut.inspect().find(button: \u0026#34;Tell me another!\u0026#34;).tap() wait(for: [expectation], timeout: 0.1) // 2 XCTAssertEqual( requestExecutorInvokedWithRequest?.url, .init(URL(string: \u0026#34;https://official-joke-api.appspot.com/random_joke\u0026#34;)!) ) } // 1 - Definition of XCTestExpectation. // 2 - Waiter that waits in that line for the expectation to be fulfilled and continue execution. // 3 - fulfill method called in a asynchronous context to make sure the async code it\u0026rsquo;s finished before assertion. We run the test again, and again (there is an option in Xcode to run the test multiple times e.g 100 times) just to be sure it works in a deterministic way. Spoiler - it should be all time green now ✅\nAll that is left now is the refactoring. This requestExecutor closure in the production code does not look cool to me. My suggestion is creating URLSessionProtocol instead ⤵️\nprotocol URLSessionProtocol { func data( for request: URLRequest, delegate: (any URLSessionTaskDelegate)? ) async throws -\u0026gt; (Data, URLResponse) } extension URLSession: URLSessionProtocol {} Later, we can inject it into JokeView instead of requestExecutor closure and then use it ⤵️\nstruct JokeView: View { ... private let urlSession: URLSessionProtocol init( ... urlSession: URLSessionProtocol = URLSession.shared ) { ... self.urlSession = urlSession } var body: some View { ... } func fetchNewJoke() { let url = URL(string: \u0026#34;https://official-joke-api.appspot.com/random_joke\u0026#34;).unsafelyUnwrapped let request = URLRequest(url: url) Task { try! await urlSession.data(for: request, delegate: nil) } } } The test also requires some changes. First URLSessionSpy definition ⤵️\nclass URLSessionSpy: URLSessionProtocol { var dataForRequestExpectation: XCTestExpectation? private(set) var dataInvokedWithRequest: [URLRequest] = [] func data(for request: URLRequest, delegate: (any URLSessionTaskDelegate)?) async throws -\u0026gt; (Data, URLResponse) { dataInvokedWithRequest.append(request) dataForRequestExpectation?.fulfill() return (Data(), URLResponse()) } } and then changes in the test ⤵️\nfunc test_whenTappingOnTheButton_ItTriggersRequest() throws { let expectation = XCTestExpectation(description: \u0026#34;Wait for request being executed\u0026#34;) // 1 let urlSessionSpy = URLSessionSpy() urlSessionSpy.dataForRequestExpectation = expectation // 2 let sut = JokeView(state: .loaded(joke: .udp), urlSession: urlSessionSpy) try sut.inspect().find(button: \u0026#34;Tell me another!\u0026#34;).tap() wait(for: [expectation], timeout: 0.1) XCTAssertEqual( urlSessionSpy.dataInvokedWithRequest.map(\\.url?.absoluteString), [\u0026#34;https://official-joke-api.appspot.com/random_joke\u0026#34;] ) } // 1 - We still need XCTestExpectation to be sure the assertion is always called after the request is executed. // 2 - URLSessionSpy has API for setting the XCTestExpectation that is fullfilled when data for request method is being called. After the refactor we verify the implementation by running all tests to ensure tests are green 🟢\nSummary Today we made a small step towards having the all requirements implemented in TDD. In theory it\u0026rsquo;s only one short test, but we discovered how to trigger button action based on its title using ViewInspector. I dare say we busted (again 😅) the myth about SwiftUI code being not testable.\nCode coverage The final outcome: the code coverage for Jokes.app is 100% A few extra final notes on the code coverage ⤵️\nCode coverage it\u0026rsquo;s a value that does not guarantee your code works 100% correctly, and it shouldn\u0026rsquo;t be chased or set as any goal. Code coverage serves more like a helper to a developer providing rough information about percentage of code lines triggered in a test suite. Additionally, it can show you not tested pieces of code when you\u0026rsquo;re not coding in TDD or point you to old not used code. Remember that this pure statistic value looks good on presentations and charts, but it won\u0026rsquo;t defend you when you design tests in a bad maner! - That\u0026rsquo;s why I decided to start the series where we\u0026rsquo;re going to workout the best to handle testing SwiftUI code. 💯\nThe app is not complete, so stay tuned as there\u0026rsquo;s more to come! I\u0026rsquo;m gonna be sharing even more tips and tricks 📚\nResources Github repository with the code: https://github.com/Zaprogramiacz/JokesApp/tree/TDD-with-SwiftUI-Triggering-API-request ViewInspector: https://github.com/nalexn/ViewInspector Joke API: https://github.com/15Dkatz/official_joke_api Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/testing-swiftui-code-in-tdd/testing-swiftui-code-in-tdd-button-action-request/","summary":"\u003ch2 id=\"recap\"\u003eRecap\u003c/h2\u003e\n\u003cp\u003eHello everyone and welcome to the next chapter of the series about SwiftUI code automated testing!\u003c/p\u003e\n\u003cp\u003eIn the previous post we\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003edefined acceptance criteria for the Joke app that we\u0026rsquo;re implementing\u003c/li\u003e\n\u003cli\u003ewe covered by snapshot tests all UI cases mentioned in the acceptance critieria\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThat\u0026rsquo;s what the app looks like ⤵️\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"joke_app_designs\" loading=\"lazy\" src=\"/posts/series/testing-swiftui-code-in-tdd/testing-swiftui-code-in-tdd-button-action-request/images/joke_app_designs.png\"\u003e\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s the link to the previous blog post ⤵️ (Worth reading before this one)\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.mobiledevdiary.com/posts/testing-swiftui-code-the-beginning/\"\u003eTesting SwiftUI Code - The beginning (UI)\u003c/a\u003e\u003c/p\u003e","title":"TDD with SwiftUI - Triggering API request"},{"content":"Swift Testing can elevate your unit tests writing 🚀\nHello Apple Developer! I prepared a special post that will help you write better unit tests using the new SwiftTesting framework 🫢\nThe Swift Testing framework is the successor to XCTest for unit tests. It was introduced at this year\u0026rsquo;s WWDC24 and is worth learning 📚\nOne of the main features of Swift Testing are parameterized tests 🧪\nClick to learn how to leverage this new feature ⤵️\nInsights about Swift Testing Tags\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/swift-testing-parametrized-tests/","summary":"\u003cp\u003eSwift Testing can elevate your unit tests writing 🚀\u003c/p\u003e\n\u003cp\u003eHello Apple Developer! I prepared a special post that will help you write better unit tests using the new SwiftTesting framework 🫢\u003c/p\u003e\n\u003cp\u003eThe Swift Testing framework is the successor to XCTest for unit tests. It was introduced at this year\u0026rsquo;s WWDC24 and is worth learning 📚\u003c/p\u003e\n\u003cp\u003eOne of the main features of Swift Testing are parameterized tests 🧪\u003c/p\u003e\n\u003cp\u003eClick to learn how to leverage this new feature ⤵️\u003c/p\u003e","title":"Swift Testing parametrized tests"},{"content":"Today, I have a special post about Apple\u0026rsquo;s new testing framework - Swift Testing! 🤩\nSwift Testing was presented at WWDC24 as a new, modern, simplified framework for writing automated tests. It\u0026rsquo;s a perfect candidate to replace XCTest unit tests, so it\u0026rsquo;s definitely worth learning 🧑‍🏫\nThe topic of Swift Testing is quite broad, so I decided to break it down into more digestible parts, starting with Swift Testing Tags. What is this? 🤔\nClick to find out ⤵️\nInsights about Swift Testing Tags\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/insights-about-swift-testing-tags/","summary":"\u003cp\u003eToday, I have a special post about Apple\u0026rsquo;s new testing framework - Swift Testing! 🤩\u003c/p\u003e\n\u003cp\u003eSwift Testing was presented at WWDC24 as a new, modern, simplified framework for writing automated tests. It\u0026rsquo;s a perfect candidate to replace XCTest unit tests, so it\u0026rsquo;s definitely worth learning 🧑‍🏫\u003c/p\u003e\n\u003cp\u003eThe topic of Swift Testing is quite broad, so I decided to break it down into more digestible parts, starting with Swift Testing Tags. What is this? 🤔\u003c/p\u003e","title":"Insights about Swift Testing Tags"},{"content":"Intro This year’s WWDC is just around the corner, and I decided to write about how I ended up at WWDC in 2017. At that time, I was a computer science student and Apple was organizing Swift Student Challange for which I was eligible for. As a young iOS apprentice, I couldn\u0026rsquo;t miss this opportunity - I signed up. How did I get there? That\u0026rsquo;s what I want to share with you today. ⤵️\nScholarship submission How the scholarship submission looked like in 2017? It was split into two parts:\nSwift Playground - the goal was to create a project using Swift Playground showcasing something interesting. submission form - completed form with few written answers. I cannot re-call what were the exact questions, but it was something in \u0026ldquo;Why should we choose you?\u0026rdquo; style. Pixel balls My idea for the Playground was creating a mini game in UIKit.\nThe rules are very simple. You need to choose one ball on the playground and move it to empty field. After making a move next 3 balls are displayed at the playground. You have to set a minimum 5 balls vertically, horizontally or diagonally. After that the balls disappear and you gain points.\nWay to the empty field can be blocked by another balls. In this situation you can\u0026rsquo;t place there any balls.\nYou can find out how it looks here ⤵️\nWhat made my submission unique? In my opinion it was:\npixel art design - I created all assets pixel by pixel including the title and balls and everything that looks pixel style. 👾 custom pixel font - yes, first time (and so far only) I created my own font to make it looks pixel style in the way I imagined it. 💭 unit tests - that might be surprising too, especially for the iOS apprentice. Have you ever seen unit tests written in Swift Playground? If not - have a look here ⤵️ Result As you probably already guessed, I was accepted. 🎉\nFinal words I\u0026rsquo;m glad that I could take part in such a great and important event for Swift community. After 7 years, I can hardly believe that I was actually sitting there watching Keynote live. I hope to go back there someday and once again have an opportunity to meet iOS coders from all over the world. 🌎\nBefore writing the blog post, I updated my submission project - Pixel Balls 👾 to run in Xcode 15.4. You can find it here - https://github.com/Zaprogramiacz/PixelBalls-WWDC2017\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/my-wwdc-2017-scholarship-submission/","summary":"\u003ch2 id=\"intro\"\u003eIntro\u003c/h2\u003e\n\u003cp\u003eThis year’s WWDC is just around the corner, and I decided to write about how I ended up at WWDC in 2017. At that time, I was a computer science student and Apple was organizing Swift Student Challange for which I was eligible for. As a young iOS apprentice, I couldn\u0026rsquo;t miss this opportunity - I signed up. How did I get there? That\u0026rsquo;s what I want to share with you today. ⤵️\u003c/p\u003e","title":"My WWDC 2017 Scholarship submission"},{"content":"Intro Hello everyone and welcome to my first (ever) blog series!\nToday, I\u0026rsquo;m going to begin experimenting with SwiftUI. The mission is to build a small application and having it fully tested 💯. I decided to go for that quest to broaden my knowledge around SwiftUI and verify the rumors that it cannot be tested.\nTo keep it relatively readable I decided to split it up and we\u0026rsquo;re going to see how many parts we end up with.\nWhat I planned so far:\ndefining the requirements writing tests and implementation of UI writing tests and implementation (in TDD) of business logic \u0026hellip; and something more, maybe try to re-shape the app to follow Redux architecture 🤔 Let\u0026rsquo;s get this journey started and move to the crème de la crème of the blog post!\nChapter I - Requirements Our goal will be to build a very simple app that displays jokes fetched from the API.\nWe\u0026rsquo;re going to start from the very beginning - describing requirements that we\u0026rsquo;ll be implemented.\nBusiness logic As a user I can see a joke a joke is displayed in the following format: setup ⤵️ punchline example:\nThe punchline often arrives before the set-up. ⤵️ Do you know the problem with UDP jokes? the text is aligned to center under the joke there is a button named \u0026ldquo;Tell me another!\u0026rdquo; over the text there is a header in a form of the image that is centered As a user I can get a new joke when you tap the button \u0026ldquo;Tell me another!\u0026rdquo;, the API request fetching a new random joke is triggered during the request a joke is replaced with loading indication text - \u0026ldquo;Making up a joke 🤭\u0026rdquo; when the request is successful a new joke in a place of loading indication text is displayed (\u0026ldquo;Making up a joke 🤭\u0026rdquo;) when the request fails a joke is replaced with the following text - \u0026ldquo;I couldn\u0026rsquo;t come up with a good joke. Can I get another try? 🤔\u0026rdquo; Design (Please don\u0026rsquo;t judge me design skills 😅)\nAs we already agreed that I\u0026rsquo;m not an expert in desing, we won\u0026rsquo;t be focusing on making it pixel perfect. We want to have all the elements look the same like on the design, but spacing can be a bit different, and we\u0026rsquo;re ok with it.\nAPI For API we\u0026rsquo;re going to use Official Joke API. Where the response format looks ⤵️\nGET https://official-joke-api.appspot.com/random_joke { \u0026#34;type\u0026#34;: \u0026#34;programming\u0026#34;, \u0026#34;setup\u0026#34;: \u0026#34;The punchline often arrives before the set-up.\u0026#34;, \u0026#34;punchline\u0026#34;: \u0026#34;Do you know the problem with UDP jokes?\u0026#34;, \u0026#34;id\u0026#34;: 73 } Chapter II - UI In that chapter we\u0026rsquo;re going to make snapshots of our views using the SnapshotTesting (https://github.com/pointfreeco/swift-snapshot-testing) library.\nBased on our requirements we can clearly see that UI has three states: joke (when a joke is displayed), loading and error. We\u0026rsquo;ll be implemeting each of them one by one starting from the snapshot tests, providing the implementation and verifying it by tests (It\u0026rsquo;s the flow that I consider close to TDD).\nIf you don\u0026rsquo;t know how snapshot tests work I suggest reviewing the library docs https://github.com/pointfreeco/swift-snapshot-testing or in one of my blog posts ➡️ \u0026ldquo;Testing\u0026rdquo; section.\nLet\u0026rsquo;s start with the first snapshot test covering state when a joke is displayed ⤵️\nfunc test_JokeView_DisplaysJoke() { let joke = Joke( setup: \u0026#34;The punchline often arrives before the set-up.\u0026#34;, punchline: \u0026#34;Do you know the problem with UDP jokes?\u0026#34; ) let sut = JokeView(joke: joke) let controller = UIHostingController(rootView: sut) assertSnapshot(of: controller, as: .image(on: .iPhone13Pro), record: true) } After adding it, the project doesn\u0026rsquo;t compile - it\u0026rsquo;s expected, because we still haven\u0026rsquo;t defined a few components yet. It\u0026rsquo;s the next step ⤵️\nstruct Joke { let setup: String let punchline: String } struct JokeView: View { let joke: Joke init(joke: Joke) { self.joke = joke } var body: some View { Text(\u0026#34;Hello world\u0026#34;) } } Now the code compiles and we can run the tests. As a result we should get failure (we\u0026rsquo;re in the record mode) and the reference image with \u0026ldquo;Hello world\u0026rdquo; text in the center of the white screen. It\u0026rsquo;s a good first step towards the real implementation.\nOne of the features introduced together with SwiftUI are previews offering us live reloading when developing views.\nIn the UIKit, I\u0026rsquo;d double check the reference snapshot after updating the view not to waste time on running simulator all over again, but here we can use previews.\nTo avoid copy-paste anti-pattern, we\u0026rsquo;ll extract the view setup from the snapshot tests to the shared component ⤵️\nenum JokeViewPreviewProvider { // MARK: - Views static let jokeView = JokeView(state: .loaded(joke: joke)) // MARK: - Models static let joke = Joke( setup: \u0026#34;The punchline often arrives before the set-up.\u0026#34;, punchline: \u0026#34;Do you know the problem with UDP jokes?\u0026#34; ) } after extraction we refactor the test to the form ⤵️\nfunc test_JokeView_DisplaysJoke() { let controller = UIHostingController(rootView: JokeViewPreviewProvider.jokeView) assertSnapshot(of: controller, as: .image(on: .iPhone13Pro), record: true) } Before starting real UI implementation we can setup the preview ⤵️\n#Preview { JokeViewPreviewProvider.jokeView } and finally focus on the view implementation.\nstruct JokeView: View { let joke: Joke init(joke: Joke) { self.joke = joke } var body: some View { VStack { Image(\u0026#34;header\u0026#34;) VStack(alignment: .center) { Text(joke.setup) Text(\u0026#34;⤵️\u0026#34;) Text(joke.punchline) }.multilineTextAlignment(.center) .frame(height: 180) .padding(.horizontal, 64) .padding(.top, 16) Button(action: { print(\u0026#34;Button tapped\u0026#34;) }) { Text(\u0026#34;Tell me another!\u0026#34;) .tint(.black) .padding(.vertical, 10) }.frame(maxWidth: .infinity) .background(Color(\u0026#34;ButtonBackground\u0026#34;)) .clipShape(Capsule()) .overlay(Capsule().stroke(style: StrokeStyle(lineWidth: 3))) .padding(.horizontal, 32) .padding(.top, 16) } } } When it\u0026rsquo;s ready we can re-record the reference image and check if the view renders correctly.\nHaving a closer look at the reference image you can notice that something is wrong with the corners of the button 🤔\nAccording to the snapshoting library documentation some components such as UIPickerView, UIAppearance, UIVisualEffect require to be added to the key window to render correctly. If you don\u0026rsquo;t do this your snapshot won\u0026rsquo;t look perfect (like the one above ⬆️).\nAs described above, we have to use the snapshotting strategy that renders snapshot in a key window ⤵️\nassertSnapshot( of: controller, as: .image(drawHierarchyInKeyWindow: true, size: ViewImageConfig.iPhone13Pro.size), record: true ) When we check the reference image now it displays correctly ⤵️\nand if everything is perfect 👌🏻, remove record: true from the assertSnapshot function, re-run the tests, and check the result ✅\nTo review if test works correctly, make any change in the view, e.g change the color of the button background to .red and run the test. It should fail ❌. Revert the failing changes to make it green again, commit and let\u0026rsquo;s move on 👏🏻\nThe next state that has to be handled is loading. Just like before, start by wrting a test ⤵️\nfunc test_JokeView_LoadingJoke() { let controller = UIHostingController(rootView: JokeViewPreviewProvider.jokeLoading) assertSnapshot( of: controller, as: .image(drawHierarchyInKeyWindow: true, size: ViewImageConfig.iPhone13Pro.size), record: true ) } The code does not compile, so we are in the red stage, so let\u0026rsquo;s make it compile first.\nFirst, introduce the struct JokeState with two cases and inject it to the JokeView instead of Joke ⤵️\nenum JokeState { case loading case loaded(joke: Joke) } struct JokeView: View { let state: JokeState init(state: JokeState) { self.state = state } ... } then, define loading view in preview providers ⤵️\nenum JokeViewPreviewProvider { // MARK: - Views static let jokeView = JokeView(state: .loaded(joke: joke)) static let loadingView = JokeView(state: .loading) // MARK: - Models static let joke = Joke( setup: \u0026#34;The punchline often arrives before the set-up.\u0026#34;, punchline: \u0026#34;Do you know the problem with UDP jokes?\u0026#34; ) } and handle new state in the view\u0026rsquo;s body, by inserting EmptyView there ⤵️\nvar body: some View { VStack { Image(\u0026#34;header\u0026#34;) VStack(alignment: .center) { switch state { case .loading: EmptyView() case .loaded(let joke): Text(joke.setup) Text(\u0026#34;⤵️\u0026#34;) Text(joke.punchline) } }.multilineTextAlignment(.center) .frame(height: 180) .padding(.horizontal, 64) .padding(.top, 16) Button(action: { print(\u0026#34;Button tapped\u0026#34;) }) { Text(\u0026#34;Tell me another!\u0026#34;) .tint(.black) .padding(.vertical, 10) }.frame(maxWidth: .infinity) .background(Color(\u0026#34;ButtonBackground\u0026#34;)) .clipShape(Capsule()) .overlay(Capsule().stroke(style: StrokeStyle(lineWidth: 3))) .padding(.horizontal, 32) .padding(.top, 16) } } We reached the stage where the test compiles, so we can now run the test. The output is expected - failure ❌, because the record mode is turned on. The recorded reference image doesn\u0026rsquo;t have any text what\u0026rsquo;s expected, because loading state is modeled by EmptyView.\nIf the test already compiles, we can move on to add the next preview ⤵️\n#Preview { JokeViewPreviewProvider.loadingView } Now we can implement the loading state handling according to the requirements and trigger the test to re-record the reference image ⤵️\nvar body: some View { VStack { Image(\u0026#34;header\u0026#34;) VStack(alignment: .center) { switch state { case .loading: Text(\u0026#34;Making up a joke 🤭\u0026#34;) case .loaded(let joke): Text(joke.setup) Text(\u0026#34;⤵️\u0026#34;) Text(joke.punchline) } }.multilineTextAlignment(.center) .frame(height: 180) .padding(.horizontal, 64) .padding(.top, 16) Button(action: { print(\u0026#34;Button tapped\u0026#34;) }) { Text(\u0026#34;Tell me another!\u0026#34;) .tint(.black) .padding(.vertical, 10) }.frame(maxWidth: .infinity) .background(Color(\u0026#34;ButtonBackground\u0026#34;)) .clipShape(Capsule()) .overlay(Capsule().stroke(style: StrokeStyle(lineWidth: 3))) .padding(.horizontal, 32) .padding(.top, 16) } } If the results and requirements are satisfactory, we can disable the record mode (by removing record: true from the test) and commit.\nThe last state is failure. We repeat each step just like for the previous states.\nAdd test ⤵️\nfunc test_JokeView_LoadingJokeFailure() { let controller = UIHostingController(rootView: JokeViewPreviewProvider.jokeLoadingFailure) assertSnapshot( of: controller, as: .image(drawHierarchyInKeyWindow: true, size: ViewImageConfig.iPhone13Pro.size), record: true ) } Make the code compile 💻\nFailure case in JokeState ⤵️\nenum JokeState { case loading case loaded(joke: Joke) case failure } Changes in the preview provider ⤵️\nenum JokeViewPreviewProvider { // MARK: - Views static let jokeView = JokeView(state: .loaded(joke: joke)) static let loadingView = JokeView(state: .loading) static let jokeLoadingFailure = JokeView(state: .failure) // MARK: - Models static let joke = Joke( setup: \u0026#34;The punchline often arrives before the set-up.\u0026#34;, punchline: \u0026#34;Do you know the problem with UDP jokes?\u0026#34; ) } The new state handling in the view\u0026rsquo;s body ⤵️\nvar body: some View { VStack { Image(\u0026#34;header\u0026#34;) VStack(alignment: .center) { switch state { case .loading: Text(\u0026#34;Making up a joke 🤭\u0026#34;) case .loaded(let joke): Text(joke.setup) Text(\u0026#34;⤵️\u0026#34;) Text(joke.punchline) case .failure: EmptyView() } }.multilineTextAlignment(.center) .frame(height: 180) .padding(.horizontal, 64) .padding(.top, 16) Button(action: { print(\u0026#34;Button tapped\u0026#34;) }) { Text(\u0026#34;Tell me another!\u0026#34;) .tint(.black) .padding(.vertical, 10) }.frame(maxWidth: .infinity) .background(Color(\u0026#34;ButtonBackground\u0026#34;)) .clipShape(Capsule()) .overlay(Capsule().stroke(style: StrokeStyle(lineWidth: 3))) .padding(.horizontal, 32) .padding(.top, 16) } } Run the test, and record the initial reference image.\nAdd the preview ⤵️\n#Preview { JokeViewPreviewProvider.jokeLoadingFailure } Implement the state according to the criteria ⤵️\nvar body: some View { VStack { Image(\u0026#34;header\u0026#34;) VStack(alignment: .center) { switch state { case .loading: Text(\u0026#34;Making up a joke 🤭\u0026#34;) case .loaded(let joke): Text(joke.setup) Text(\u0026#34;⤵️\u0026#34;) Text(joke.punchline) case .failure: Text(\u0026#34;I couldn\u0026#39;t come up with a good joke. Can I get another try? 🤔\u0026#34;) } }.multilineTextAlignment(.center) .frame(height: 180) .padding(.horizontal, 64) .padding(.top, 16) Button(action: { print(\u0026#34;Button tapped\u0026#34;) }) { Text(\u0026#34;Tell me another!\u0026#34;) .tint(.black) .padding(.vertical, 10) }.frame(maxWidth: .infinity) .background(Color(\u0026#34;ButtonBackground\u0026#34;)) .clipShape(Capsule()) .overlay(Capsule().stroke(style: StrokeStyle(lineWidth: 3))) .padding(.horizontal, 32) .padding(.top, 16) } } Run the test again, check the new reference image ⤵️\nIf everything is alright, remove record: true from the test, run it again to verify it\u0026rsquo;s ✅ and commit!\nSummary The final outcome: the code coverage for JokeView is 98.8% the untested code is the button action. All the UI requirements are covered and the code is ready to have business logic implemented. The testing suite consists of three snapshots verifying the correctness of all states.\nIn the next blog post we\u0026rsquo;re going to unpack even more interesting topic 🫢 - I\u0026rsquo;m going to try to implement all the business logic in TDD! 💯\nResources Github repository with the code: https://github.com/Zaprogramiacz/JokesApp Snapshot testing library: https://github.com/pointfreeco/swift-snapshot-testing Joke API: https://github.com/15Dkatz/official_joke_api Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/series/testing-swiftui-code-in-tdd/testing-swiftui-code-the-beginning/","summary":"\u003ch2 id=\"intro\"\u003eIntro\u003c/h2\u003e\n\u003cp\u003eHello everyone and welcome to my first (ever) blog series!\u003c/p\u003e\n\u003cp\u003eToday, I\u0026rsquo;m going to begin experimenting with SwiftUI. The mission is to build a small application and having it fully tested 💯. I decided to go for that quest to broaden my knowledge around SwiftUI and verify the rumors that it cannot be tested.\u003c/p\u003e\n\u003cp\u003eTo keep it relatively readable I decided to split it up and we\u0026rsquo;re going to see how many parts we end up with.\u003c/p\u003e","title":"Testing SwiftUI Code - The beginning (UI)"},{"content":"Intro Combine is a framework made by Apple designed to support us in writing code that could be way more complex if written in an imperative way. It\u0026rsquo;s often said that with great power comes great responsibility. Therefore, as developers, it’s essential for us to understand how to harness it, so it does not backfire.\nToday, we\u0026rsquo;re going to take a closer look 👀 at a few Combine operators, showcasing their practical application.\nOperators map public func map\u0026lt;T\u0026gt;( _ transform: @escaping (Self.Output) -\u0026gt; T ) -\u0026gt; Publishers.Map\u0026lt;Self, T\u0026gt; In Combine, the map operator transforms each value from the upstream applying the provided transformation closure.\nThe definition might sound complex but let\u0026rsquo;s have a quick look how simple it is in practice ⤵️.\n[1, 2, 3] .publisher .map { String(\u0026#34;Number: \\($0)\u0026#34;) } .sink(receiveValue: { print(\u0026#34;RECEVIED VALUE: \\($0)\u0026#34;) }) .store(in: \u0026amp;cancellables) Console output RECEVIED VALUE: Number: 1 RECEVIED VALUE: Number: 2 RECEVIED VALUE: Number: 3 In the above example, we map Int values received from the upstream to String values and then we print them in the receiveValue closure.\nEasy, right? Let\u0026rsquo;s jump to flatMap then!\nflatMap public func flatMap\u0026lt;T, P\u0026gt;( maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) -\u0026gt; P ) -\u0026gt; Publishers.FlatMap\u0026lt;P, Self\u0026gt; where T == P.Output, P : Publisher, Self.Failure == P.Failure The flat map operator transforms each value from the upstream into a new Publisher applying the provided transformation closure.\nClear? Yass 🤯\nCheck out the image for a more detailed explanation ⤵️.\n(1) - events in a stream. The first emitted event is number 1 (2) - stream completion (3) - streams (4) - each new upstream event triggers subscription to a new stream (5) - events observed by the flatMap subscriber (6) - each of upstream events is republished to the down stream Still unclear? I come to the rescue 🛟 with the code example ⤵️.\nvar cancellables = Set\u0026lt;AnyCancellable\u0026gt;() func request(number: Int) -\u0026gt; AnyPublisher\u0026lt;Int, Never\u0026gt; { // 1 Just(number) .delay(for: .seconds((1...2).randomElement()!), scheduler: DispatchQueue.main) // 2 .handleEvents( receiveSubscription: { _ in print(\u0026#34;REQUEST \\(number) STARTED\u0026#34;) }, receiveCompletion: { switch $0 { case .finished: print(\u0026#34;REQUEST: \\(number) FINISHED\u0026#34;) case .failure: fatalError() } }, receiveCancel: { print(\u0026#34;REQUEST \\(number) CANCELLED\u0026#34;) } ) .eraseToAnyPublisher() } Array([1, 2, 3]) // 3 .publisher // 4 .flatMap { number in request(number: number) } // 5 .sink( // 6 receiveCompletion: { switch $0 { case .finished: print(\u0026#34;STREAM FINISHED\u0026#34;) case .failure: fatalError() } }, receiveValue: { value in print(\u0026#34;RECEIVED VALUE \\(value)\u0026#34;) } ) .store(in: \u0026amp;cancellables) Console output REQUEST 1 STARTED REQUEST 2 STARTED REQUEST 3 STARTED RECEIVED VALUE 1 REQUEST 1 FINISHED RECEIVED VALUE 3 REQUEST 3 FINISHED RECEIVED VALUE 2 REQUEST 2 FINISHED STREAM FINISHED // 1 - request function emits a single value (using Just publisher) representing the result of a faked API request. The result is published with a delay to simulate the real async request // 2 - delay operator delaying the flow of elements through the stream for a given amount of time and publishing using a specified scheduler // 3 - definition of static array of elements // 4 - publisher operator transforming a static array of elements into a stream of the array elements. Each element is then sent down the stream one by one (one event per each element) // 5 - flatMap operator taking each element from the upstream and transforms it into a new stream. In our case, it takes each element of the array (// 3) and transforms it into a new stream using the request function (// 1) // 6 - sink operator attaching the subscriber to the stream and observes elements published by it. In our case, it observes and prints out each received value and the stream completion What can be a real life usage of flatMap?\nImagine that you have a messaging app. To get a single thread with all messages you need to perform one request, so in case you have 10 threads, you perform 10 requests, to get each thread.\npseudocode ⤵️\ngetThreadsIDs() // 1 .flatMap(\\.publisher) .map(ThreadDetailsRequest.init) // 2 .flatMap { request in apiService.execute(request: request) // 3 .catch { _ in Empty(completeImmediately: true) } // 4 } .sink(receiveValue: { response in threadDetailsStorage.insert(response.threadDetails) }) // 5 .store(in: \u0026amp;cancellables) // 1 - stream that publishes the Array of threads IDs (AnyPublisher\u0026lt;[String], Error\u0026gt;) // 2 - mapping ID (String) to a request model // 3 - each request is converted into a new publisher that represents API request // 4 - flatMap does not ignore errors! In that case catch operator intercepts an error, and replaces it with Empty publisher. Empty publisher does not emit any error or element into downstream, but completes the stream. In case we don\u0026rsquo;t handle errors, the main stream would be completed with error and no more events would be observed // 5 - sink operator that attaches subscriber to the stream and observe elements in the main stream. In our case we save fetched thread details into local cache map + switchToLatest public func switchToLatest() -\u0026gt; Publishers.SwitchToLatest\u0026lt;Self.Output, Self\u0026gt; According to the docs ⤵️\nRepublishes elements sent by the most recently received publisher.\n~ Apple Docs\nCheck out the schema for more details ⤵️\n(most of the elements are already described on the flatMap schema 👀)\n(1) - when a new publisher arrives from the upstream, switchToLatest cancels the previous publisher subscription and subscribes to a new one Code example ⤵️\nvar cancellables = Set\u0026lt;AnyCancellable\u0026gt;() func request(number: Int) -\u0026gt; AnyPublisher\u0026lt;Int, Never\u0026gt; { Just(number) .delay(for: .seconds((1...2).randomElement()!), scheduler: DispatchQueue.main) .handleEvents( receiveSubscription: { _ in print(\u0026#34;REQUEST \\(number) STARTED\u0026#34;) }, receiveCompletion: { switch $0 { case .finished: print(\u0026#34;REQUEST \\(number) FINISHED\u0026#34;) case .failure: fatalError() } }, receiveCancel: { print(\u0026#34;REQUEST \\(number) CANCELLED\u0026#34;) } ) .eraseToAnyPublisher() } Array([1, 2, 3]) .publisher .map { number in request(number: number) } // 1 .switchToLatest() // 2 .sink( receiveCompletion: { switch $0 { case .finished: print(\u0026#34;STREAM FINISHED\u0026#34;) case .failure: fatalError() } }, receiveValue: { value in print(\u0026#34;RECEIVED VALUE \\(value)\u0026#34;) } ) .store(in: \u0026amp;cancellables) Console output REQUEST 1 STARTED REQUEST 1 CANCELLED REQUEST 2 STARTED REQUEST 2 CANCELLED REQUEST 3 STARTED RECEIVED VALUE 3 REQUEST 3 FINISHED STREAM FINISHED // 1 - conversion of each number to AnyPublisher\u0026lt;Int, Never\u0026gt; representing an API request // 2 - switchToLatest subscribes to the latest publisher emitted from the upstream and cancels the previous subscription Real-life use case for map + switchToLatest\npseudocode ⤵️\nsearchBar .publisher(for: .text) // 1 .map(SearchRequest.init) // 2 .map { request in apiService.execute(request: request) // 3 .catch { _ in Empty(completeImmediately: true) } // 4 } .switchToLatest() // 5 .sink(receiveValue: { result in dataSource = result.searchResults }) // 6 .store(in: \u0026amp;cancellables) // 1 - publisher of elements from the search bar text field // 2 - each text element is mapped to the request model (SearchRequest) // 3 - each request is converted into a new publisher representing the API request // 4 - switchToLatest does not ignore errors! In that case catch operator intercepts an error and replaces it with Empty publisher. Empty publisher does not emit any error or element downstream but completes the stream. In case errors are not handled, the mainstream would be completed with error and no more events would be observed // 5 - subscribes to the latest publisher from the upstream and cancels the previous subscription // 6 - sink operator attaches the subscriber to the stream and observes elements in the mainstream. In our case, the data source is modified by assigning the request result. What\u0026rsquo;s the difference comparing to the flatMap?\nflatMap republishes all publisher\u0026rsquo;s events and map + switchToLatest only the latest one (previous ones are cancelled).\nWhat\u0026rsquo;s the benefit of using map + switchToLatest?\nUsing map + switchToLatest may help you with reducing API operations when the previous operation becomes redundant.\nFinal Takeaways 🧠 Think about your use case and a stream behavior that you want to achieve. flatMap subscribes to each new publisher and lets them all republish elements to the downstream. map + switchToLatest lets the latest publisher republish elements to the downstream, the rest of the streams are cancelled. It may help you with reducing redundant API requests. flatMap, map + switchToLatest do not ignore errors. In case any publisher fails it\u0026rsquo;ll end the mainstream and no more events will be published. To make sure it does not happen to you, remember about error handling. When a stream is cancelled no more events will be published (including completion). Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/combine-flatmap-map-switchtolatest-demystified/","summary":"\u003ch2 id=\"intro\"\u003eIntro\u003c/h2\u003e\n\u003cp\u003eCombine is a framework made by Apple designed to support us in writing code that could be way more complex if written in an imperative way. It\u0026rsquo;s often said that with great power comes great responsibility. Therefore, as developers, it’s essential for us to understand how to harness it, so it does not backfire.\u003c/p\u003e\n\u003cp\u003eToday, we\u0026rsquo;re going to take a closer look 👀 at a few Combine operators, showcasing their practical application.\u003c/p\u003e","title":"Combine: flatMap, map + switchToLatests (flatMapLatest) demystified"},{"content":"We’re just after this year’s WWDC where we had a chance to witness the unveiling of a new persistence framework called SwiftData. Naturally, I couldn’t resist delving deeper into it. One particular topic that caught my attention was the observation of local storage using Query and its testability.\nSwiftData SwiftData makes it easy to persist data using declarative code. You can query and filter data using regular Swift code. And it’s designed to integrate seamlessly with SwiftUI.\nApple docs\nStarting using SwiftData in your app is incredibly straightforward.\nStep one: Create a persistable model. To do it use a new Model macro that under the hood converts the class into a model manageable by SwiftData.\n@Model class Flashcard { let front: String let back: String } 👀 Hint: To reveal what’s hidden under the macro use Xcode built-in option to expand the macro.\nThe macro annotation is the only modification that is actually needed to make your model persistable! 🤯\nStep two: Define a container and propagate model context to SwiftUI views using .modelContainer(for: any PersistentModel) view modifier.\nA Model Container is an object that, based on the given schema and configuration, manages how your app data is stored in a database.\nWindowGroup { FlashcardSetsView() }.modelContainer( for: [Flashcard.self] ) That makes your app ready to work with SwiftData.\nAlongside SwiftData, Apple has rolled out an interesting Query property wrapper that lets you to observe your local cache state. By using it, all updates of your entities (insert / update / delete) are reflected in SwiftUI views.\nSince I believe that learning happens best through hands-on examples, let’s try to apply new knowledge and build a foundation for a flashcard app.\nQuery property wrapper Let’s assume that we have to build a flashcard app. Our main goal is checking the usage of Query property wrapper and it’s testability, so to simplify let’s focus only on the screen displaying flashcard sets list.\n⚠️ In the blog post I’ll be using a shorter version of the code. In the end there is a link to the Github repository where you can check the working version presented here.\nFirst, let’s define our persistable models. To achieve that we can use the Model macro.\n@Model class Flashcard { let front: String let back: String } @Model class FlashcardsSet { let name: String var flashcards: [Flashcard] = [] } We defined two models. The first represents a single flashcard, the second groups flashcards into sets. A flashcard set may have many flashcards. The relation between the entities is handled automatically by SwiftData after defining a flashcards property in the FlashcardSet model.\nAn implementation of a simple list to display them shouldn’t be a problem! 💪🏻\nstruct FlashcardSetsView: View { let flashcardSets: [FlashcardsSet] var body: some View { List { Section { ForEach(flashcardSets) { flashcardsSet in HStack { Text(flashcardsSet.name) Spacer() Text(\u0026#34;Cards: \\(flashcardsSet.flashcards.count)\u0026#34;) } } } } .navigationTitle(\u0026#34;Flashcard Sets\u0026#34;) } } The result is a view with list of flashcard sets ⤵️\nNow let’s modify our views to observe local cache updates by using the Query property wrapper.\nstruct FlashcardSetsView: View { @Query(sort: \\.name, order: .forward) var flashcardSets: [FlashcardsSet] // 1 var body: some View { List { Section { ForEach(flashcardSets) { flashcardsSet in HStack { Text(flashcardsSet.name) Spacer() Text(\u0026#34;Cards: \\(flashcardsSet.flashcards.count)\u0026#34;) } } } } .navigationTitle(\u0026#34;Flashcard Sets\u0026#34;) } } // 1 — property wrapper that loads, filters, sorts and observes changes of your local cache. In the given example we observe cache updates on FlashcardsSet entity and sort results alphabetically using name property.\nThe last thing is a container definition\n@main struct FlashcardsApp: App { var body: some Scene { WindowGroup { NavigationStack { FlashcardSetsView() } }.modelContainer(for: [FlashcardsSet.self, Flashcard.self]) // 1 } } // 1 — manages an app’s schema and model storage configuration. We register our container together with the FlashcardsSet and Flashcard models that we’d like to persist.\nEverything is setup and your persistence layer is now fully integrated with the view layer! It wasn’t hard right? 🥱\nTo demonstrate that the integration works let’s add one more section that generates FlashcardsSet when a button is tapped.\nstruct FlashcardSetsView: View { @Query(sort: \\.name, order: .forward) var flashcardSets: [FlashcardsSet] @Environment(\\.modelContext) private var context // 1 var body: some View { List { Section { Button(\u0026#34;Generate random set\u0026#34;) { context.insert(Random.flashcardsSet()) // 2 } } Section { ForEach(flashcardSets) { flashcardsSet in HStack { Text(flashcardsSet.name) Spacer() Text(\u0026#34;Cards: \\(flashcardsSet.flashcards.count)\u0026#34;) } } } } .navigationTitle(\u0026#34;Flashcard Sets\u0026#34;) } } // 1 — model context that is propagated to views after the model container declaration using .modelContainer(for: PersistentModel.Type)\n// 2 — new flashcards set generation using factory function and inserting it into the context. After the insert you don’t have to call save() SwiftData handles it for you.\nAs a result we get an additional section with the button triggering generation of a new random item on the list ⤵️\nLet’s move on to the cherry 🍒 on top, which is testing the Query property wrapper.\nTesting Based on my previous experience with SwiftUI I know that it’s impossible to fully test a View. Hence the question: Can I test SwiftData and the usage of Query property wrapper?\nThe answer is — Yes, indeed! 🤩\nThe snapshot testing library comes to the rescue 🙏🏻 https://github.com/pointfreeco/swift-snapshot-testing\nSnapshot testing it’s an approach involving capturing elements of a user interface (UI) and storing them as reference images. During each test run, a new snapshot of the given UI element is taken and compared against the stored reference image. This technique allows developers to quickly identify unintended visual changes that might be caused by broken layout or bugs inside business logic.\nLet’s begin by writing our first test.\n@MainActor func testDisplayingFourInsertedSets() { // 1 let configuration = ModelConfiguration(inMemory: true) // 2 let inMemoryContainer = try! ModelContainer( for: [Flashcard.self, FlashcardsSet.self], configuration ) // 3 let context = inMemoryContainer.mainContext // 4 let sut = NavigationStack { // 5 FlashcardSetsView() .modelContainer(inMemoryContainer) // 6 } let quantumMechanic100 = FlashcardsSet(name: \u0026#34;Quantum Mechanic\u0026#34;, numberOfFlashcards: 100) let nanoscience30 = FlashcardsSet(name: \u0026#34;Nanoscience\u0026#34;, numberOfFlashcards: 30) let nuclearPhysics70 = FlashcardsSet(name: \u0026#34;Nuclear Physics\u0026#34;, numberOfFlashcards: 70) let nanotechnology5 = FlashcardsSet(name: \u0026#34;Nanotechnology\u0026#34;, numberOfFlashcards: 5) [quantumMechanic100, nanoscience30, nuclearPhysics70, nanotechnology5].forEach { flashcardsSet in context.insert(flashcardsSet) // 7 } let viewController = UIHostingController(rootView: sut) // 8 assertSnapshot( matching: viewController, as: .image(on: .iPhone13Pro), named: \u0026#34;four_flashcard_sets_inserted\u0026#34; ) // 9 } // 1 — The @MainActor attribute guarantees that the function will be executed on the main thread. The function has to be marked with @MainActor, because inside we use the container’s mainContext property that is marked with @MainActor. Without the mark the code doesn’t compile, because you are attempting to access a main actor-isolated property from a non-isolated context, and Swift compiler detects that.\n// 2 — by default data are stored on a disk. To simplify tests and avoid shared fixture problem we use in-memory storage that is cleaned after each test.\n// 3 — manages an app’s schema and model storage configuration. We register our container together with the FlashcardsSet and Flashcard models that we’d like to persist. We pass the configuration as an argument specifying us that the container has to store data in-memory.\n// 4 — context that lets us seed our database.\n// 5 — the flashcard sets view has defined navigation title requiring navigation view to be displayed and rendered on a snapshot.\n// 6 — propagation of in-memory container to the view.\n// 7 — seeding in-memory database with test data.\n// 8 — wrapping SwiftUI View in UIHostingController to use Snapshotting strategy designed for UIViewController. It’s possible to use a strategy designed for SwiftUI view, but the UIKit one renders navigation title better.\n// 9 — snapshot test taking view controller to snapshot, snapshotting strategy (in our case it’s an image rendered using iPhone 13 Pro UI specifications like size, traits and safe area) and name describing a snapshot.\nWe run the test and get failure ❌\nIt’s expected, because the first phase of the snapshot test is recording a reference image ⤵️\nIn the reference image we see that four objects inserted to a local cache are displayed, so we are successful 🥳\nAfter the test re-run you should get ✅\nNow, let’s explore another scenario to double-check updates coming from the cache. In this test we’re going to delete one of the previously inserted objects and expect the snapshot to not have the deleted object.\n@MainActor func testDisplayingFourInsertedSetsAndRemovingOne() { // 1 ... // The previous version of the test collapsed context.delete(nuclearPhysics70) // 2 try! context.save() // 3 assertSnapshot( matching: viewController, as: .image(on: .iPhone13Pro), named: \u0026#34;four_flashcard_sets_inserted_one_removed\u0026#34; ) // 4 } // 1 — we update the previously written test description to better describe what behaviour we test.\n// 2 — deletion of previously inserted object.\n// 3 — to have deletion changes displayed on a snapshot we have to save context manually.\n// 4 — definition of the next snapshot in the scope of the same test.\nWe run the test and voilà 🎉\nWe get the next reference image without a deleted object ⤵️\nThe integration of SwiftData and SwiftUI has been successfully tested 🎉\nSummary We’ve been eagerly awaiting a modern successor to CoreData, and finally, we have a promising pretender on the horizon — SwiftData.\nIt’s fantastic to see Apple’s ongoing dedication to the advancement of SwiftUI and the introduction of new frameworks complementing it.\nNevertheless, it would be beneficial to see further advancements in the development of tools assisting developers in their day-to-day work, such as automated testing tools (especially unit testing), to keep pace with the rapid evolution of SwiftUI.\nUseful links Github repo with the code — https://github.com/Zaprogramiacz/SwiftDataQuery. Running it requires Xcode 15 beta ⚠️ SwiftData docs — https://developer.apple.com/xcode/swiftdata/ Meet SwiftData — https://developer.apple.com/videos/play/wwdc2023/10187/ Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/testing-swiftdata-and-the-query-property-wrapper-through-an-example/","summary":"\u003cp\u003eWe’re just after this year’s WWDC where we had a chance to witness the unveiling of a new persistence framework called SwiftData. Naturally, I couldn’t resist delving deeper into it. One particular topic that caught my attention was the observation of local storage using Query and its testability.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"intro_image\" loading=\"lazy\" src=\"/posts/testing-swiftdata-and-the-query-property-wrapper-through-an-example/images/intro_image.jpeg\"\u003e\u003c/p\u003e\n\u003ch3 id=\"swiftdata\"\u003eSwiftData\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSwiftData makes it easy to persist data using declarative code. You can query and filter data using regular Swift code. And it’s designed to integrate seamlessly with SwiftUI.\u003c/p\u003e","title":"Testing SwiftData and the Query property wrapper through an example"},{"content":"A tool written in Ruby which can keep your project and a .pbxproj file clean. A valuable ally when solving complex conflicts inside the project file.\nKeeping a project file clean during a project life could be tough especially while working in a team, and we conflict our branches from time to time. Solving multiple conflicts in a .pbxproj can lead to mistakes that can cause duplicated references, not removed old references, or files not referenced by a project, but still existing in a project directory. In today’s article, I’ll share my tips on how to deal with described problems pretty easily.\nsynx According to docs synx is\nA command-line tool that reorganizes your Xcode project folder to match your Xcode groups.\nThe tool is written in Ruby and distributed via Ruby gems, which makes the installation process simple\n$ gem install synx Before execution make sure that you have your changes committed.\nAfter the installation, all you need is to pass a path to your .xcodeproj file\n$ synx SynxExampleApp.xcodeproj Voilà! You should see how an output is generated (time of execution depends on the number of files in a project).\nWhat’s just happened? Missing files detected. AFile.swift and ZFile.swift are referenced by the project, but swift files are missing. Not referenced files detected. Files structure contains one more directory (Details), which is not referenced by the project, but the directory with subfolders and files exists on the disc. Files sorted alphabetically and by type Duplicated and invalid references in .pbxproj were removed Now fix all the issues listed in an output and enjoy a clean .pbxproj file.\nBonus I bet that everyone who uses Xcode and works together with a bigger team on the one project has had conflicts which they basically didn’t know to solve. Those conflicts occur very often when project structure is changed on different branches.\nThanks to the above mentioned benefits regarding fixing .pbxproj file issues there is one more interesting usage of this tool. It can be used for solving complex conflicts inside .pbxproj file.\nWhen dealing with conflicts in the .pbxproj choose a correct option only if you know exactly that you’re right. Otherwise, go for the version “choose both”.\nAfter going through all the conflicts save a file and run synx. Based on an output clean old files or create missing references.\nThe tool is not designed for resolving conflicts in the way described here, so it’s likely that it won’t always be possible to solve super complex conflicts by using it.\nThanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"https://www.mobiledevdiary.com/posts/keep-your-project-clean-using-synx/","summary":"\u003cp\u003eA tool written in Ruby which can keep your project and a .pbxproj file clean. A valuable ally when solving complex conflicts inside the project file.\u003c/p\u003e\n\u003cp\u003eKeeping a project file clean during a project life could be tough especially while working in a team, and we conflict our branches from time to time. Solving multiple conflicts in a .pbxproj can lead to mistakes that can cause duplicated references, not removed old references, or files not referenced by a project, but still existing in a project directory. In today’s article, I’ll share my tips on how to deal with described problems pretty easily.\u003c/p\u003e","title":"Keep your project clean using synx"},{"content":" Thanks for reading. 📖\nI hope you found it useful!\nIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - LinkedIn, X, Mastodon, Bluesky or via RSS feed to keep up to speed. 🚀\n","permalink":"","summary":"\u003chr\u003e\n\u003cp\u003eThanks for reading. 📖\u003c/p\u003e\n\u003cp\u003eI hope you found it useful!\u003c/p\u003e\n\u003cp\u003eIf you enjoy the topic don\u0026rsquo;t forget to follow me on one of my social media - \u003ca href=\"https://www.linkedin.com/in/maciej-gomolka/\"\u003eLinkedIn\u003c/a\u003e, \u003ca href=\"https://twitter.com/gomolka_maciej\"\u003eX\u003c/a\u003e, \u003ca href=\"https://mastodon.social/@gomolka\"\u003eMastodon\u003c/a\u003e, \u003ca href=\"https://bsky.app/profile/maciejgomolka.bsky.social\"\u003eBluesky\u003c/a\u003e or via \u003ca href=\"https://www.mobiledevdiary.com/index.xml\"\u003eRSS\u003c/a\u003e feed to keep up to speed. 🚀\u003c/p\u003e","title":""},{"content":" My adventure with the iOS platform started over eight years ago. I belong to a generation of developers who started out learning how to create mobile iOS applications in Swift, but I\u0026rsquo;m also familiar with Objective-C.\nDuring over eight years of work, I’ve created 7 applications in Swift from scratch. The experience allowed me to test many solutions, as well as design patterns (MVC, MVVM, Redux) and frameworks in practice.\nFor the last three years, I have been fighting within Proton to make the Internet a private and secure place. I was a member of Proton Mail team and currently, I\u0026rsquo;m working on a new Proton Calendar iOS app.\nMentor at open workshops and hackathons.\nI believe in words:\n“quality over quantity”\nThat\u0026rsquo;s why I love writing clean, tested code and improving code quality metrics. TDD is not only a cool methodology that we see in presentations or during conferences, but also the way of code creation I follow. I know what a refactor is and I pay attention to it by doing a very detailed code review.\nBeside being familiar with OOP, I can apply it in practice without the need to create classes with hundreds of lines. My abilities include testing UI by creating snapshot tests (so that after changing UI related code while looking for unexpected UI bugs, there’s no need to go through the whole app manually).\nWhile writing a code, I follow the correct style and conventions of Swift, which is why I’m using Swiftlint linter for every project.\nAs a code-writing lover I don’t like wasting time waiting for the project to be built, so I know how to reduce this time to a minimum by modularizing projects.\nI’m a big fan of reactive programming, so I implement it where necessary.\nI believe that there are better solutions than manual release process or manual check of code correctness before merging. Therefore, I can configure CI / CD pipeline using fastlane and Danger tools to automate manual processes.\nI know that high quality does matter and I’m sure that it benefits in the long term. That’s why while programming I’m doing my best to avoid creating buggy and not fully working features.\n","permalink":"https://www.mobiledevdiary.com/about/","summary":"\u003cfigure class=\"align-center \"\u003e\n    \u003cimg loading=\"lazy\" src=\"maciej.png#center\"\n         alt=\"Maciej\" width=\"70%\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eMy adventure with the iOS platform started over \u003cstrong\u003eeight years\u003c/strong\u003e ago. I belong to a generation of developers who started out learning how to create mobile iOS applications in \u003cstrong\u003eSwift\u003c/strong\u003e, but I\u0026rsquo;m also familiar with Objective-C.\u003c/p\u003e\n\u003cp\u003eDuring over eight years of work, I’ve created \u003cstrong\u003e7 applications in Swift\u003c/strong\u003e from scratch. The experience allowed me to test many solutions, as well as design patterns (MVC, MVVM, Redux) and frameworks in practice.\u003c/p\u003e","title":"About"}]