Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.
Many, many weeks ago we built a moderately complex application in SwiftUI from first principles, using only what SwiftUI gave us out of the box (part 1, part 2, part 3). We were able to get really far, really quickly, but we noticed a few problems along the way. We distilled what we observed into 5 main problems that we think are crucially important for any application architecture to solve:
SwiftUI gets us really close to solving some of these, but didn’t get us all the way there.
This inspired us to embark on exploring an architecture that gave very strong opinions on how each of these problems should be solved, and we’ve fully solved 4 of the 5 problems.
That leaves one last problem to solve, and perhaps the most important: testing. We claim that this architecture is super testable. Pretty much every facet of this architecture can be tested, and it requires very little setup work to write your first test. We will also be able to unlock lots of new ways of testing that are very difficult to achieve without a cohesive and pervasive architecture in your application.
So, let’s start by reminding ourselves what we have been building for the past many weeks.
Control the save and load effects in the favorite primes module by introducing an “Environment” for dependency injection, as covered in our episode on dependency injection.
Control the save effect inside
testSaveButtonTapped to assert that when the reducer is called, it returns the expected effect.
Making such an assertion involves introducing local, mutable state to the test that is changed in a predictable way if the controlled effect runs. For example, you could introduce a boolean that flips from
true when the effect runs. Then, after you run the effect returned from the reducer (you can use its
sink method), you can assert that it changed as expected.
Further assert that the save effect:
Completes. You can use the overload of
sink that provides a
receivedCompletion handler to hook into this event. Use the
wait methods on the test case to handle this asynchrony.
Does not return an action to be fed back into the store. You can introduce an assertion to the
receivedValue block that fails if it runs.
Control the load effect inside
testLoadButtonTapped to assert that when the reducer is called, it returns the expected effect.
Assert the load effect completes, as well.
Further, rather than manually feeding the expected
loadedFavoritePrimes action back into the reducer, extract and feed the action returned by the load test effect instead. Again, use the
wait methods to handle this asynchrony.
Continuing the previous exercises, control the nth prime API effect in the counter module such that you can test:
Elm is both a pure functional language and framework for creating web applications in a declarative fashion. It was instrumental in pushing functional programming ideas into the mainstream, and demonstrating how an application could be represented by a simple pure function from state and actions to state.
The idea of modeling an application’s architecture on simple reducer functions was popularized by Redux, a state management library for React, which in turn took a lot of inspiration from Elm.
A talk that Brandon gave at the 2017 Functional Swift conference in Berlin. The talk contains a brief account of many of the ideas covered in our series of episodes on “Composable State Management”.