Dynamically altering the environment at runtime to enable debug features e.g. switches for enabling mocked API-responses

Hi I know this is not critical to apps in production, but I think it is very beneficial for both myself and testers to be able to 'cheat' at runtime in debug builds to e.g. set the app in a certain state or make the api return a certain value to test behavior. This would require altering the Environment to be done right, but for one the changes would not propagate due to Environment being a struct, but also there really isn't api for this.

Right now I have to put this 'cheating' functionality in the reducers along with the production code, which I would like to avoid.

How do others go about debug switches and such?

1 Like

What about making a higher-order reducer with just this “cheating” functionality? There you can store additional state and use additional actions for each of those cheats. And the environment can be kept just the same.

That way in debug builds you can apply to your app reducer and in production builds you can completely avoid that work :slight_smile:

1 Like

@victor That... is actually a neat idea! It could work for some cases, however I think it would worry me a tiny bit that I would not in fact be exercising my reducer-logic and thereby not actually be showing what would happen if the api returned this or that. But I will look into it and see what I can come up with that is as close to real world as possible. Thanks for the idea!

In addition to @victor's idea there is a way to change the environment even though the environment is immutable and dependencies in the environment tend to the structs.

Since the dependencies in the environment tend to be simple structs with closure properties, those closures can close over some mutable state that is only visible inside the scope that creates instances of the dependency.

For example, say you had an ApiClient that had a few endpoints for request information from your API, but you also wanted an endpoint for changing the base URL of the API, that way while the app is running you could switch between staging servers and production servers. That client could have this kind of interface:

struct ApiClient {
  var setBaseUrl: (URL) -> Effect<Never, Never>
  var fetchEpisodes: () -> Effect<[Episode], ApiError>
  var favoriteEpisode: (Episode.Id) -> Effect<Void, ApiError>
}

And then the live implementation of that client could hold onto a bit of mutable baseUrl: URL state so that it could be changed whenever the setBaseUrl endpoint is called:

extension ApiClient {
  static func live(baseUrl: URL) -> Self {
    var baseUrl = baseUrl

    return Self( 
      setBaseUrl: { url in 
        .fireAndForget { baseUrl = url } 
      },
      fetchEpisodes: { 
        // make request using baseUrl
      },
      favoriteEpisode: { id in 
        // make request using baseUrl
      }
    )
  }
}

This is a style we have used quite a bit, and it keeps these seemingly messy customization endpoints in the effect layer, which means its still possible to test and verify that endpoints are being called the way you expect.

1 Like

Ah, yes, this will definitely come in handy! One could add an e.g. setDesiredPaymentResponse endpoint where the api client could close over an optional hard coded response, which would be returned when a payment endpoint was called. All of this only in DEBUG of course :sweat_smile: Thanks for this!

Terms of Service

Privacy Policy

Cookie Policy