Dependency management in The Composable Architecture using ReducerProtocol

Dependency management in The Composable Architecture using ReducerProtocol

By Atikur Rahman, Lead iOS Developer at Evangelist Apps

Earlier we discussed about the ReducerProtocol, which is a new way to create reducers in The Composable Architecture (TCA). With the release of the ReducerProtocol, the library provides a new dependency management system. Prior to ReducerProtocol, we had to define Environment type to hold all the dependencies for our feature. But, that’s no longer needed. Now reducers are types, so they are more natural place to hold onto dependencies.

Let’s go through an example to see how to use this new dependency management system. In our example, let’s consider that we need to show a random quote from a web service.

First step is to model interface for our dependency. Let’s declare a struct called ApiClient with a mutable property called getRandomQuote.

struct ApiClient {
var getRandomQuote: () async throws -> String

This can be considered as interface for an endpoint that returns a String value.

Next, we register our dependency with the library. For that, we need to conform the ApiClient to the DependencyKey protocol. By conforming to the DependencyKey protocol, we need to provide implementation for the liveValue property.

extension ApiClient: DependencyKey {
static var liveValue = ApiClient(
getRandomQuote: {
// code to get data from web service
// this code is just for demonstration purpose, ignoring any error handling or best practices.
let url = URL(string: "")
let (data, _) = try await url!)
return String(decoding: data, as: UTF8.self)

Here we make live network requests to get data from the web service.

Next, we need to add a computed property to DependencyValue with getter and setter implementation.

extension DependencyValues {
var apiClient: ApiClient {
get { self[ApiClient.self] }
set { self[ApiClient.self] = newValue }

Now that we have registered the ApiClient dependency with the library, we can add the dependency using @Dependency property wrapper and use the dependency from the reducer -

struct MyFeature: ReducerProtocol {
struct State: Equatable {
var quote = ""
var isRequestInFlight = false

enum Action: Equatable {
case getQuoteButtonTapped
case quoteResponse(String)

@Dependency(\.apiClient) var apiClient

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .getQuoteButtonTapped:
state.isRequestInFlight = true
return .run { send in
try await send(.quoteResponse(apiClient.getRandomQuote()))

case let .quoteResponse(quote):
state.quote = quote
state.isRequestInFlight = false
return .none

The getQuoteButtonTapped simulates tapping of a button on the user interface. When that happens, we send an EffectTask from the getQuoteButtonTapped action case. We use the run static method to construct EffectTask. Within the trailing closure of the .run method, we perform the network request by using the ApiClient. This happens asynchronously. When the api response is received, the quoteResponse action case is called.

It’s also really easy to write unit tests for the reducers that depends on the external clients. For live application, we would want to make the network request and get actual data. But for writing unit tests, we need to control certain aspects and we would want to use mock clients instead of making live network requests.

So when we will be writing unit tests for getQuoteButtonTapped, we will want to avoid network calls. It’s really easy to provide a new implementation of the dependency while writing unit tests. We need to provide a trailing closure to TestStore called withDependencies, which allows us to override any dependency we want -

func testRandomQuote() async {
let dummyQuote = "This is a random quote"
let store = TestStore(initialState: MyFeature.State()) {
} withDependencies: {
$0.apiClient.getRandomQuote = { dummyQuote }

await store.send(.getQuoteButtonTapped) {
$0.isRequestInFlight = true
await store.receive(.quoteResponse(dummyQuote)) {
$0.quote = dummyQuote
$0.isRequestInFlight = false

As you can see, here we override getRandomQuote implementation and return a static string instead of making network request. This makes it really easy to write unit tests for actions involving side effects.

Thanks for reading!

Please follow us on Twitter and LinkedIn for more updates.

#SwiftUI #evangelistapps #TheComposableArchitecture #swift #iOS #coding #iOS16

Dependency management in The Composable Architecture using ReducerProtocol was originally published in Evangelist Apps Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Book a Discovery Session First!