Proposal to allow adapters to be switched at runtime in tests.
Example:
We have an EntityStore which has functions to save and find entities.
In our tests we want the ability to use different implimentations (real, dummy, in-memory etc.) in different tests.
We might want to use the real adapter in acceptance tests, a dummy one just to assert the adapter is called, and an in-memory adapter for all other tests (because it's faster).
- real - the one used in production, persists to postgres
- dummy - sends a message when each function is called so test can assert_received to check the function was called, doesn't actually store any data
- in-memory - same as real but uses in-memory data store
(potentially the dummy and in-memory could be combined in to one adapter)
Each adapter would impliment the same behaviour.
To switch between adatpers in tests, at runtime, we might have a "switchable" adapter which is an adatper which delegates to another adapter, which can be changed at runtime. The switching of an adapter will not be global but only effect the test process.
# In config/dev.exs
config :shared, :entity_store, Shared.EntityStore.Persistent # real
# In config/test.exs
config :shared, :entity_store, Shared.EntityStore.Switchable # switchable (can switch to any adapter)
# In config/prod.exs
config :shared, :entity_store, Shared.EntityStore.Persistent # realWhen using the entity store we would do so in the normal way:
defmodule SomeHandler do
@entity_store Application.get_env(:shared, :entity_store)
def call(...) do
@entity_store.get(...)
end
endThis is the production adapter and stores data in postgres.
defmodule Shared.EntityStore.Persistent do
@behaviour Behaviours.EntityStore
# ...
endThis entity store allows switching between different entity store implimentations, and delegates to whichever is currenly being used.
defmodule EntityStore.Switchable do
@behaviour Behaviours.EntityStore
@default_adapter @entity_store Application.get_env(:shared, :entity_store)
def use(adapter), do: Process.set(:adapter, adatper)
def using(), do: Process.get(:adatper, @default_adapter)
def get(id) do
using().get(id)
end
# ...
end(we would proberbly use ProcessTree.get so this works even if further processes are spawned from the test)
defmodule EntityStore.InMemory do
@behaviour Behaviours.EntityStore
def get(id), do: ....
end
This could use a list of map in the Process to store state.
we can switch to any of the adapters we like...
setup, do: EntityStore.Switchable.use(InMemory)
EntityStore.Switchable.use(Dummy)
EntityStore.Switchable.use(Persistent)