View Source Multiverses (multiverses v0.11.0)

Elixir introduces into the world of programming, the "multiverse testing" pattern. This is a pattern where integration tests are run concurrently and each test sees a shard of global state.

pre-existing-examples

Pre-Existing Examples:

  • Mox: each test has access to the global module mock, sharded by the pid of the running test.
  • Ecto: each test has access to a "database sandbox", which is a checked out transaction on the global database that acts as its own database shard.
  • Hound,Wallaby: each test generates an ID that is passed outside of the BEAM that is reintercepted on ingress, this ID is then used to connect ecto sandboxes to the parent test PID

This library implements Multiverses-aware versions of several constructs in the Elixir Standard Library that aren't natively Multiversable.

For plugins that are provided for other systems, see the libraries:

  • :multiverses_http - which extends this to HTTP requests that exit the BEAM.
  • :multiverses_pubsub - which extends this to Phoenix.PubSub

The following multiverse modules are provided by this core package:

usage

Usage

In mix.exs, add the following dependency:

{:multiverses, "~> 0.11.0", only: :test}

in-your-code

In your code

For example, if you would like to use the Multiverses version of the Application module (Multiverses.Application), add the following lines:

To config/config.exs:

config :my_app, Application, Application

To config/test.exs:

config :my_app, Application, Multiverses.Application

To the module where you would like to use multiverses Application:

@application Application.compile_env(:my_app, Application)

And where you would like to make a multiverses Application call:

def some_function do
  value = @application.get_env(:my_app, :some_env_variable)
  # ...
end

in-your-tests

In your tests

  1. Register the module you'd like to substitute with multiverses.
setup do
  Multiverses.shard(Application)
end
  1. Your tests have segregated application values!
defmodule MyModule do
  @application Multiverses.Application
  def get_and_wait(value) do
    @application.put_env(:my_app, :env, value)
    Process.sleep(1000)
    @application.get_env(:my_app, :env)
  end
end

defmodule SomeTest do
  use ExUnit.Case, async: true
  test do
    assert :foo == MyModule.get_and_wait(:foo)
  end
end

defmodule SomeOtherTest do
  use ExUnit.Case, async: true
  test do
    assert :bar == MyModule.get_and_wait(:bar)
  end
end

Link to this section Summary

Functions

Utility version of allow/3 that lets you batch-assign multiple allowances

Inspired by Mox.allow/3, this function assigns a process or registered name process to be put into the shard of a pid or directly into a shard.

Temporarily assigns the running process to the shard, within the scope of the provided lambda. This is done through the process dictionary. Other processes will not be aware that this process has been added to the shard.

Obtains the universe id for the current process.

Creates a new shard for a particular domain module and assigns this pid to the shard. You can batch assigning multiple shards as well.

Returns a list of multiverse domain modules and the respective shard-ids associated with those domain modules.

Link to this section Types

Link to this section Functions

See Multiverses.Server.all/1.

@spec allow([{module(), id()}], term()) :: [{{module(), pid()}, id()}]

Utility version of allow/3 that lets you batch-assign multiple allowances

Link to this function

allow(module, owner_pid, allowed_via)

View Source
@spec allow(module(), pid() | id(), pid()) :: [{{module(), pid()}, id()}]

Inspired by Mox.allow/3, this function assigns a process or registered name process to be put into the shard of a pid or directly into a shard.

usage

usage

The following is the most common use case, called from the test process, where you want to allow a spawned process to be put into the same shard as the test process.

Multiverses.allow(Application, self(), child_pid)

In some cases you may want an allowance to occur from within the process that needs access to the shard. In this case, do the following:

Multiverses.allow(Application, multiverse_id, self())
Link to this function

allow_for(module, id, fun)

View Source
@spec allow_for(module(), id(), (-> result)) :: result when result: term()

Temporarily assigns the running process to the shard, within the scope of the provided lambda. This is done through the process dictionary. Other processes will not be aware that this process has been added to the shard.

Link to this function

id(module, options \\ [])

View Source
@spec id(module(), options :: keyword()) :: id() | nil

Obtains the universe id for the current process.

This is found by checking process and the entries in the :$callers process dictionary entry to find if any of them are registered.

If the current process is not registered, then it raises Multiverses.UnexpectedCallError

options

Options

  • :strict (defaults to true): if false, returns nil, instead of crashing.
@spec shard(module() | [module()]) :: [{module(), id()}]

Creates a new shard for a particular domain module and assigns this pid to the shard. You can batch assigning multiple shards as well.

@spec shards(pid()) :: [{module(), id()}]

Returns a list of multiverse domain modules and the respective shard-ids associated with those domain modules.