Profile avatar
webdavis.bsky.social
Hi, I’m Stephen 👋. I like to talk about software architecture and test-driven development 🧪 Check out my work on https://github.com/webdavis/webdavis
60 posts 20 followers 29 following
Getting Started
Active Commenter
comment in response to post
How are you liking it so far? (That’s the resource I am considering for getting started with SwiftUI)
comment in response to post
Dope! Any hot takes from the lesson?
comment in response to post
This took me forever to wrap my head around and it all boiled down to my lack of understanding of the Swift language. The more you know!
comment in response to post
Now here's an enum *without* associated value types. Since there are no associated value types the compiler says we can compare instances of this Error type and slaps on the Equatable protocol at compile time.
comment in response to post
As it turns out, in Swift, enums with no associated values get free Equatable synthesis! Here is an enum *with* associated value types:
comment in response to post
So what the heck was going on? Somehow, the compiler is saying that RemoteFeedLoader.Error conforms to Equatable. This led me to believe that the compiler was auto-synthesizing RemoteFeedLoader.Error's conformance to Equatable. But what conditions need to be met in order for that to happen?
comment in response to post
We could explicitly conform RemoteFeedLoader.Error to Equatable like so, but apparently we don't need to? 🤔
comment in response to post
Here's the FeedLoader protocol's implementation class, RemoteFeedLoader. No where in this code did we explicitly conform the Error type to Equatable:
comment in response to post
On the surface it makes sense: LoadFeedResult is Equatable if the Error in the failure case is Equatable. This means that the protocol's associatedtype Error need to conform to Equatable, which means that whatever class implements the protocol need to have an Equatable Error type.
comment in response to post
If we uncomment the conditional conformance (the commented out line above), then the compiler error goes away 🫰, and we can compare LoadFeedResult types using XCTAssertEqual.
comment in response to post
The puzzle 🧩: Whenever I commented out the following line in the interface boundary, I would then get the accompanying compile-time error in the test code:
comment in response to post
No worries haha, still fun 👏
comment in response to post
This is really interesting and reminds me how developers go from user stories to use cases. Seems every domain has its techniques for dialing in. Great stuff!
comment in response to post
😆 I won’t be passing any Ophthalmology tests, but I enjoyed the UI! Great work!
comment in response to post
1. What testing framework I should use. 2. If it’s is a class-per-test framework (e.g. the JUnit approach) or not. 3. If the language supports interfaces/protocols. If not, then what does it use to achieve composition?
comment in response to post
This is some next level nerdom. Bravo 👏
comment in response to post
Argghh! I said all this and realized that I didn't make it very clear that I am referring specifically to the "try?" syntax. You can see it in the first code snippet as follows: let root = try? JSONDecoder().decode(Root.self, from: data) (I'M TIRED lol)
comment in response to post
Lolz 😂
comment in response to post
Seems kinda convoluted! Also, now we have to force unwrap root. Maybe that's why the Swift team implemented this. (Side note: I usually hate reading documentation, but the Swift team actually does a really good job at it.) (7/7)
comment in response to post
(6/7) Now let's see what it would be like to implement the same behavior without this syntax. We could convert this to a do-catch block, like so:
comment in response to post
Here is what the Apple docs say about it: "You use try? to handle an error by converting it to an optional value. If an error is thrown while evaluating the try? expression, the value of the expression is nil." (5/7)
comment in response to post
This is because Swift allows us to convert errors to optional values. In this case we would rather throw our own domain-specific error. We don't care about the DecodingError thrown by decode(). So instead, if decode throws an error we instruct Swift to convert it to an optional value `nil`. (4/7)
comment in response to post
(3/7) Take a look at the following static function. Why isn't there a do-catch block to catch the potential error that JSONDecoder().decode throws?
comment in response to post
Converting an error to an optional value is a great way to handle situations where we want to throw a domain-specific error in favor of a third-party error that we don't control (e.g. DecodingError thrown by JSONDecoder().decode). (2/7)
comment in response to post
Do you have any favorites from the freecodecamp podcast that you recommend?
comment in response to post
Hey Zero, Thanks for connecting! I followed you back 🥳 I totally get it—these topics are tricky for me too! That’s why I post about them; rubber ducking helps me process. If something’s unclear, it might mean I haven’t explained it well. Feel free to ask or point it out! Let’s learn together 🙂
comment in response to post
And here I thought it was just a way to escape reality (I'm cooked 🥴)
comment in response to post
Good stuff! Really important topic. I'm sure at some point you'll come across closures and FP (if you haven't already). Interfaces are just named contracts, while closures are unnamed contracts. They do the same thing, but folks like to argue about it online lol. Anywho, have fun!
comment in response to post
These are just some ideas. You have decide what tests make sense for your app! And remember, tests aren't just for you. They are for your coworkers, and the person that comes along to fill your shoes once you've moved on to your next role. (11/11)
comment in response to post
🔄 Concurrency tests to check how the app handles multiple simultaneous API requests. ⚠️ Edge case tests to test the API with unusual or edge-case input data to see how the app handles it. (10/11)
comment in response to post
🔍 Data integrity tests to check that the structure and types of the data returned match the expected format. 🔒 Authentication/Authorization tests to ensure the app properly handles authentication failures or unauthorized access. (9/11)
comment in response to post
So what are some tests we can add? ⏱️ Response time tests to ensure the API responds within an acceptable time frame. ❌ Error handling tests to verify that the app properly handles API errors (e.g., 4xx, 5xx responses). (8/11)
comment in response to post
(Side-Quest) 🧪 What are End-to-End Tests? These are tests that check our system against other services, like remote APIs. They hit the real services that our app uses. For example, we might make a real network request to that Weather API using a real instance of Foundation.URLSession. (7/11)
comment in response to post
However, if replacing the API isn't possible, then we can mitigate this issue by adding *more* end-to-end tests, and increasing how frequently they run in Continuous Integration. If we are using GitHub Actions as our CI server, then we maybe we run these tests everyday, or even twice a day! (6/11)
comment in response to post
(Full stop, if this is the case, then we should be looking for a new API. If our app is designed to be composable, replacing the API shouldn't be too difficult.) (5/11)
comment in response to post
📌 For Example Let's say our app relies on a remote API service (like a Weather API), and the service owners introduce breaking changes in their next update without giving us adequate forewarning. (4/11)
comment in response to post
How do rely more on end-to-end tests? 🤔 Simply put, by writing more of them, and increasing the frequency that they run in CI. (3/11)
comment in response to post
Let's say our app uses a service that we don't trust. How do we deal with it? The less we trust a counterpart (e.g., APIs, remote servers, databases, file systems), the more we should rely on end-to-end tests to get ahead of breakage and reduce risks. (2/11)
comment in response to post
💻 Here's the Example Code:
comment in response to post
This is the Open/Closed Principle (OCP) in action! We don't have to change the view controller to extend it's behavior. If we want to add more behavior to the view controller, then we just add another adapter that implements the delegate:
comment in response to post
Apple implemented the delegate pattern so that we can customize a view controller’s behavior. By implementing the DataSource/Delegate, we can define exactly how it should function. In other words, it's a boundary for behavior. Hey, thanks Apple! 🍎
comment in response to post
Without it, a view controller's behavior would always be fixed, and Apple's framework wouldn't be very useful 😔
comment in response to post
🔑 The Solution Map the API module’s model to the Interface/Boundary model within the API module itself. This ensures modules remain independent and prevents leaking implementation details to parts of the system that don’t need them. Keep it clean and focused! (4/4)
comment in response to post
⚠️ Avoid Tight Coupling with Decodable ⚠️ Implementing Decodable directly on the model in our Interface/Boundary may unintentionally couple our API module to other modules, such as the caching or database module(s). (3/4)