View Source Cheatsheet
This cheatsheet provides simple examples for how to use Patch, for more details see the linked documentation.
installation
Installation
add-patch-to-your-dependencies
Add Patch to your Dependencies
In the deps/0
function in the mix.exs file add a line for Patch.
mix.exs
def deps do
[
{:patch, "~> 0.12.0", only: [:test]}
]
end
optionally-including-excluding-imports
Optionally Including / Excluding Imports
:only
will cause only a subset of symbols to be imported
test/example_only_test.exs
defmodule ExampleOnlyTest do
use ExUnit.Case
use Patch, only: [:expose, :patch, :private]
# ... snip the rest of the module ...
end
:except
will import all symbols except the ones specified
test/example_except_test.exs
defmodule ExampleExceptTest do
use ExUnit.Case
use Patch, except: [:fake, :history]
# ... snip the rest of the module ...
end
use-patch-in-your-test-case
Use Patch in your Test Case
In any ExUnit.Case based Test Case add a line to use Patch.
test/example_test.exs
defmodule ExampleTest do
use ExUnit.Case
use Patch
# ... snip the rest of the module ...
end
aliasing-imports
Aliasing Imports
:alias
allows the test author to import a symbol while renaming it.
test/example_alias_test.exs
defmodule ExampleAliasTest do
use ExUnit.Case
use Patch, alias: [patch: :mock]
# ... snip the rest of the module ...
end
patching
Patching
scalars
Scalars
test "can patch with scalar values" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, "PATCHED")
assert String.upcase("Post-Patched") == "PATCHED"
end
callables
Callables
test "can patch with a callable" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, fn s -> String.length(s) end)
assert String.upcase("Post-Patched") == 12
end
cycles
Cycles
test "can patch with a cycle" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, cycle([1, 2, 3]))
assert String.upcase("Post-Patched") == 1
assert String.upcase("Post-Patched") == 2
assert String.upcase("Post-Patched") == 3
assert String.upcase("Post-Patched") == 1
assert String.upcase("Post-Patched") == 2
end
sequences
Sequences
test "can patch with a sequence" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, sequence([1, 2, 3]))
assert String.upcase("Post-Patched") == 1
assert String.upcase("Post-Patched") == 2
assert String.upcase("Post-Patched") == 3
assert String.upcase("Post-Patched") == 3
assert String.upcase("Post-Patched") == 3
end
raises
Raises
test "can patch to raise a RuntimeError" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, raises("patched"))
assert_raise RuntimeError, "patched", fn ->
String.upcase("Post-Patched")
end
end
test "can patch to raise any exception" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, raises(ArgumentError, message: "patched"))
assert_raise ArgumentError, "patched", fn ->
String.upcase("Post-Patched")
end
end
throws
Throws
test "can patch to throw a value" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, throws(:patched))
assert catch_throw(String.upcase("Post-Patched")) == :patched
end
assertions
Assertions
assert_called
assert_called
test "can assert calls on patched functions" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, "PATCHED")
assert String.upcase("Post-Patched") == "PATCHED"
assert_called String.upcase("Post-Patched")
## Arguments can be bound or pattern-matched
assert_called String.upcase(argument)
assert argument == "Post-Patched"
## The number of calls can be specified
assert_called String.upcase("Post-Patched"), 1
end
refute_called
refute_called
test "can refute calls on patched functions" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, "PATCHED")
assert String.upcase("Post-Patched") == "PATCHED"
refute_called String.upcase("Other")
end
assert_any_call
assert_any_call
test "can assert that a patched function was called with any arity" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, "PATCHED")
assert String.upcase("Post-Patched") == "PATCHED"
assert_any_call String, :upcase
end
refute_any_call
refute_any_call
test "can refute that a patched function was called with any arity" do
assert String.upcase("Pre-Patched") == "PRE-PATCHED"
patch(String, :upcase, "PATCHED")
refute_any_call String, :upcase
end
spy
spy
test "can assert / refute calls on spied modules without changing behavior" do
spy(String)
assert String.upcase("Example") == "EXAMPLE"
assert_called String.upcase("Example")
refute_called String.upcase("Other")
end
history
history
test "can retrieve the list of all calls to a patched module" do
spy(String)
assert String.upcase("Example") == "EXAMPLE"
assert String.downcase("Example") == "example"
assert history(String) == [{:upcase, ["Example"]}, {:downcase, ["Example"]}]
assert history(String, :asc) == [{:upcase, ["Example"]}, {:downcase, ["Example"]}]
assert history(String, :desc) == [{:downcase, ["Example"]}, {:upcase, ["Example"]}]
end
private-functions
Private Functions
expose
expose
test "can expose private functions for testing" do
expose(Example, private_function: 1)
assert Example.private_function(:argument) == {:ok, :argument}
end
private
private
test "can suppress warnings about calling private functions" do
expose(Example, private_function: 1)
assert private(Example.private_function(:argument)) == {:ok, :argument}
end
processes
Processes
listen
listen
test "can listen to the messages sent to a named process" do
listen(:tag, ExampleNamedProcess)
send(ExampleNamedProcess, :hello)
assert_receive {:tag, :hello}
end
test "can listen to the messages sent to a pid" do
pid = Example.start_link()
listen(:tag, pid)
send(pid, :hello)
assert_receive {:tag, :hello}
end
test "can listen to GenServer messages" do
Counter.start_link(0, name: Counter)
listen(:tag, Counter)
assert Counter.increment() == 1
assert_receive {:tag, {GenServer, :call, :increment, from}} # Bind `from`
assert_receive {:tag, {GenServer, :reply, 1, ^from}} # Match the pinned `from`
end
inject
inject
test "listeners can be injected into another processes state" do
{:ok, parent_pid} = Parent.start_link()
inject(:tag, parent_pid, [:child_pid])
assert Parent.ask_child() == :ok
assert_recieve {:tag, :ask}
end
replace
replace
test "process state can be replaced by key" do
{:ok, pid} = Example.start_link()
assert :sys.get_state(pid).field == :original
replace(pid, [:field], :updated)
assert :sys.get_state(pid).field == :updated
end