Packages

A polite, well mannered and thoroughly upstanding testing framework for Elixir

Current section

Files

Jump to
amrita lib amrita.ex
Raw

lib/amrita.ex

defmodule Amrita do
@moduledoc """
A polite, well mannered and thoroughly upstanding testing framework for Elixir.
"""
@doc """
Start Amrita for a test run.
This should be called in your test_helper.exs file.
Supports optional config:
# Use a custom formatter. Defaults to Progress formatter.
Amrita.start(formatter: Amrita.Formatter.Documentation)
"""
def start(_type, _args) do
# IO.inspect opts
import Supervisor.Spec
children = [worker(ExUnit.Server, []), worker(ExUnit.OnExitHandler, [])]
opts = [strategy: :one_for_one, name: ExUnit.Supervisor]
Supervisor.start_link(children, opts)
#
#
# Amrita.Engine.Start.now formatter: formatter
# {:ok, []}
end
def start(options \\ []) do
Amrita.Engine.Start.now(options)
end
@doc """
Polite version of start.
"""
def please_start(opts \\ []) do
start(opts)
end
defmodule Sweet do
@moduledoc """
Responsible for loading Amrita within a test module.
## Example:
defmodule TestsAboutSomething do
use Amrita.Sweet
end
"""
@doc false
defmacro __using__(opts \\ []) do
async = Keyword.get(opts, :async, false)
quote do
if !Enum.any?(__ENV__.requires, fn(x) -> x == ExUnit.Case end) do
use ExUnit.Case, async: unquote(async)
end
import ExUnit.Callbacks
import ExUnit.Assertions
import ExUnit.Case
@ex_unit_case true
use Amrita.Facts
use Amrita.Mocks
import Amrita.Checkers.Helper
import Amrita.Checkers.Simple
import Amrita.Checkers.Collections
import Amrita.Checkers.Exceptions
import Amrita.Checkers.Messages
import Amrita.Syntax.Describe
end
end
end
defmodule Facts do
@moduledoc """
Express facts about your code.
"""
@doc false
defmacro __using__(_) do
quote do
@name_stack []
import Amrita.Facts
end
end
defp fact_name(name) do
cond do
is_binary(name) -> name
is_tuple(name) -> name
true -> "#{name}"
end
end
@doc """
A fact is the container of your test logic.
## Example
fact "about addition" do
...
end
If you are using mocks you can define them as part of your fact.
## Example
fact "about mock", provided: [Flip.flop(:ok) |> true] do
Flip.flop(:ok) |> truthy
end
You can optionally examine meta data passed to each fact. Useful when used
with callbacks:
## Example
setup do
{:ok, ping: "pong"}
end
fact "with meta data", meta do
meta[:pong] |> "pong"
end
"""
defmacro fact(description, provided \\ [], var \\ quote(do: _), contents) do
var = case provided do
[provided: _] -> var
[] -> var
_ -> provided
end
quote do
deffact Enum.join(@name_stack, "") <> unquote(fact_name(description)), unquote(var) do
import Kernel, except: [|>: 2]
import Amrita.Elixir.Pipeline
unquote do
if is_list(provided) && !Enum.empty?(provided) && match?({:provided, _}, Enum.at(provided, 0)) do
{ :provided, mocks } = Enum.at(provided, 0)
quote do
provided unquote(mocks) do
unquote(contents)
end
end
else
quote do
unquote(contents)
end
end
end
end
end
end
@doc """
A fact without a body is a pending fact. Much like a TODO.
It prints a reminder when the tests are run.
## Example
fact "something thing I need to implement at somepoint"
"""
defmacro fact(description) do
quote do
deffact Enum.join(@name_stack, "") <> unquote(fact_name(description)) do
Amrita.Message.pending unquote(description)
end
end
end
@doc """
A future_fact is a pending fact. Its body is *NEVER* run.
Instead it simply prints an reminder that it is yet to be run.
## Example:
future_fact "about something that does not work yet" do
..
end
"""
defmacro future_fact(description, _ \\ quote(do: _), _) do
quote do
deffact Enum.join(@name_stack, "") <> unquote(fact_name(description)) do
Amrita.Message.pending unquote(description)
end
end
end
@doc """
facts are used to group with a name a number of fact tests.
You can nest as many facts as you feel you need.
## Example
facts "about arithmetic" do
fact "about addition" do
...
end
end
"""
defmacro facts(description, _ \\ quote(do: _), contents) do
quote do
@name_stack Enum.concat(@name_stack, [unquote(fact_name(description)) <> " - "])
unquote(contents)
if Enum.count(@name_stack) > 0 do
@name_stack Enum.take(@name_stack, Enum.count(@name_stack) - 1)
end
end
end
@doc false
defmacro deffact(message, var \\ quote(do: _), contents) do
contents =
case contents do
[do: _] ->
quote do
unquote(contents)
:ok
end
_ ->
quote do
try(unquote(contents))
:ok
end
end
var = Macro.escape(var)
contents = Macro.escape(contents, unquote: true)
quote bind_quoted: binding do
message = if is_binary(message) do
:"test #{message}"
else
:"test_#{message}"
end
ExUnit.Case.__on_definition__(__ENV__, message)
def unquote(message)(unquote(var)) do
unquote(contents)
end
def unquote(:"__#{message}__")(), do: [file: __ENV__.file, line: __ENV__.line]
end
end
end
end