ESpec is inspired by RSpec and the main idea is to be close to its perfect DSL.
It is NOT a wrapper around ExUnit but a completely new testing framework written from scratch.
Test organization with describe
, context
, it
, and etc blocks.
Familiar matchers: eq
, be_close_to
, raise_exception
, etc.
Possibility to add custom matchers.
There are two types of expectation syntax:
expect
syntax with pipe operator expect smth1 |> to(eq smth2)
or is_expected |> to(eq smth)
when subject
is defined;should
syntax: smth1 |> should(eq smth2)
or should eq smth
when subject
is defined.expect(smth1).to eq(smth2)
is deprecated and won't work with OTP 21.before
and finally
blocks (like RSpec before
and after
).
let
, let!
and subject
.
Shared examples.
Generated examples.
Async examples.
Mocks with Meck.
Doc specs.
HTML and JSON outputs.
Etc and etc.
before
and finally
before_all
and after_all
shared
datalet
and subject
assert
and refute
assert_receive
and refute_receive
capture_io
and capture_log
described_module
Add espec
to dependencies in the mix.exs
file:
def deps do
...
{:espec, "~> 1.9.2", only: :test},
...
end
mix deps.get
Then run:
MIX_ENV=test mix espec.init
The task creates spec/spec_helper.exs
Set preferred_cli_env
for espec
in the mix.exs
file:
def project do
...
preferred_cli_env: [espec: :test],
...
end
Or run with MIX_ENV=test
:
MIX_ENV=test mix espec
Place your _spec.exs
files into spec
folder. use ESpec
in the 'spec module'.
defmodule SyntaxExampleSpec do
use ESpec
it do: expect true |> to(be_true())
it do: (1..3) |> should(have 2)
end
mix espec
Run specific spec:
mix espec spec/some_spec.exs:25
Read the help:
MIX_ENV=test mix help espec
There are three macros with the same functionality: context
, describe
, and example_group
.
Context can have description and tags.
defmodule ContextSpec do
use ESpec
example_group do
context "Some context" do
it do: expect "abc" |> to(match ~r/b/)
end
describe "Some another context with opts", focus: true do
it do: 5 |> should(be_between 4, 6)
end
end
end
Available tags are:
skip: true
or skip: "Reason"
- skips examples in the context;focus: true
- sets focus to run with --focus
option.There are also xcontext
, xdescribe
, xexample_group
macros to skip example groups.
And fcontext
, fdescribe
, fexample_group
for focused groups.
'spec' module is also a context with module name as description. One can add tags for this context after use ESpec:
defmodule ContextTagsSpec do
use ESpec, skip: "Skip all examples in the module"
...
end
example
, it
, and specify
macros define the 'spec example'.
defmodule ExampleAliasesSpec do
use ESpec
example do: expect [1,2,3] |> to(have_max 3)
it "Test with description" do
4.2 |> should(be_close_to 4, 0.5)
end
specify "Test with options", [pending: true], do: "pending"
end
You can use skip
, pending
or focus
tags to control evaluation.
There are also macros:
xit
, xexample
, xspecify
- to skip;fit
, fexample
, fspecify
, focus
- to focus;pending/1
, example/1
, it/1
, specify/1
- for pending examples.defmodule ExampleTagsSpec do
use ESpec
xit "skip", do: "skipped"
focus "Focused", do: "Focused example"
it "pending example"
pending "it is also pending example"
end
The are --only
, --exclude
and --string
command line options.
One can tag example or context and then use --only
or --exclude
option to run (or exclude) tests with specific tag.
defmodule FiltersSpec do
use ESpec
context "context with tag", context_tag: :some_tag do
it do: "some example"
it "example with tag", example_tag: true do
"another example"
end
end
end
mix espec spec/some_spec.exs --only context_tag:some_tag --exclude example_tag
This runs only one test "some example"
You can also filter examples by --string
option which filter examples which contain given string in their nested description.
mix espec spec/some_spec.exs --string 'context with tag'
before
and finally
before
blocks are evaluated before the example and finally
runs after the example.
The blocks can return {:shared, key: value, ...}
or (like in ExUnit) {:ok, key: value, ...}
, so the keyword list will be saved in the dictionary and can be accessed in other before
blocks, in the example, and in finally
blocks through shared
.
You can also use a map as a second term in returned tuple: {:shared, %{key: value, ...}}
.
Example:
defmodule BeforeAndFinallySpec do
use ESpec
before do: {:shared, a: 1}
context "Context" do
before do: {:shared, %{b: shared[:a] + 1}}
finally do: "#{shared[:b]} == 2"
it do: shared.a |> should(eq 1)
it do: shared.b |> should(eq 2)
finally do: "This finally will not be run. Define 'finally' before the example"
end
end
Note, that finally
blocks must be defined before the example.
Also note that finally
blocks are executed in reverse order. Please see 'spec/before_finally_order_spec.exs' to figure out details.
There is also a short form of 'before' macro which allows to fill in shared dictionary:
before a: 1, b: 2
# which is equivalent to
before do: {shared: a: 1, b: 2}
You can configure 'global' before
and finally
in spec_helper.exs
:
ESpec.configure fn(config) ->
config.before fn(tags) -> {:shared, answer: 42, tags: tags} end #can assign values in dictionary
config.finally fn(shared) -> shared.answer end #can access assigns
end
These functions will be called before and after each example which ESpec runs.
config.before
accepts example tags as an argument. So all example tags (including tags from parent contexts) are available in config.before
. This allows you to run some specific pre-configuration based on tags.
ESpec.configure fn(config) ->
config.before fn(tags) ->
if tags[:async] || tags[:custom_tag] == :do_like_async
PrepareAsyncExecution.setup
end
{:shared, tags: tags}
end
end
before_all
and after_all
There are hooks that evaluate before and after all the examples in a module. Use this hooks for complex system setup and tear down.
defmodule BeforeAllSpec do
use ESpec
before_all do
RocketLauncher.start_the_system!
end
it do: ...
it do: ...
after_all do
RocketLauncher.stop_the_system!
end
end
Note, before_all
and after_all
hooks do not set shared
data and do not have access to them. Also note that you can define only one before_all
and one after_all
hook in a spec module.
shared
is used to share data between spec blocks. You can access data by shared.some_key
or shared[:some_key]
.
shared.some_key
will raise exception if the key 'some_key' does not exist, while shared[:some_key]
will return nil
.
The shared
variable appears in your before
, finally
, in config.before
and config.finally
, in let
and example
blocks.
before
and finally
blocks (including 'global') can modify the dictionary when return {:shared, key: value}
.
The example below illustrates the life-cycle of shared
:
spec_helper.exs
ESpec.start
ESpec.configure fn(config) ->
config.before fn -> {:shared, answer: 42} end # shared == %{answer: 42}
config.finally fn(shared) -> IO.puts shared.answer end # it will print 46
end
some_spec.exs
:
defmodule SharedBehaviorSpec do
use ESpec
before do: {:shared, answer: shared.answer + 1} # shared == %{answer: 43}
finally do: {:shared, answer: shared.answer + 1} # shared == %{answer: 46}
context do
before do: {:shared, answer: shared.answer + 1} # shared == %{answer: 44}
finally do: {:shared, answer: shared.answer + 1} # shared == %{answer: 45}
it do: shared.answer |> should(eq 44)
end
end
So, 'config.finally' will print 46
.
Pay attention to how finally
blocks are defined and evaluated.
let
and subject
let
and let!
have the same behavior as in RSpec. Both defines memoizable functions in 'spec module'. The value will be cached across multiple calls in the same example but not across examples.
let
is lazy-evaluated, it is not evaluated until the first time the function it defines is invoked.
Use let!
to force the invocation before each example.
A bang version is just a shortcut for:
let :a, do: 1
before do: a()
In example below, let! :a
will be evaluated just after before a: 1
. But let :b
will be invoked only in the last test.
defmodule LetSpec do
use ESpec
before a: 1
let! :a, do: shared.a
let :b, do: shared.a + 1
it do: expect a() |> to(eq 1)
it do: expect b() |> to(eq 2)
end
Note, The shared
is available in let
s but neither let
nor let!
can modify the dictionary.
You can pass a keyword list to let
or let!
to define several 'lets' at once:
defmodule LetSpec do
use ESpec
let a: 1, b: 2
it do: expect a() |> to(eq 1)
it do: expect b() |> to(eq 2)
end
Note, subject
and subject!
are just aliases for let :subject, do: smth
and let! :subject, do: smth
. You can use is_expected
macro (or a simple should
expression) when subject
is defined.
defmodule SubjectSpec do
use ESpec
subject(1 + 1)
it do: is_expected() |> to(eq 2)
it do: should(eq 2)
context "with block" do
subject do: 2 + 2
it do: is_expected() |> to_not(eq 2)
it do: should_not(eq 2)
end
end
There are helpers that can help you assign values from expressions that return {:ok, result} or {:error, result} tuples. For example, File.read\1
returns {:ok, binary} or {:error, reason}.
There are let_ok
(let_ok!
) and let_error
(let_error!
) functions that allow you assign values easily:
let_ok :file_binary, do: File.read("file.txt")
let_error :error_reason, do: File.read("error.txt")
One can reuse the examples defined in spec module.
defmodule SharedSpec do
use ESpec, shared: true
subject shared.hello
it do: should eq("world!")
end
shared: true
marks examples in the module as shared, so the examples will be skipped until you reuse them.
You can use the examples with it_behaves_like
or its alias include_examples
macro:
defmodule UseSharedSpec do
use ESpec
before hello: "world!"
it_behaves_like(SharedSpec)
#or
include_examples(SharedSpec)
end
You can also use let
variables from parent module in shared examples.
Use let_overridable
macro to define let
which will be overridden.
You can pass single atom, list of atoms, or keyword with default values.
See examples below.
defmodule SharedSpec do
use ESpec, shared: true, async: true
let_overridable a: 10, b: 20
let_overridable [:c, :d]
let_overridable :e
let :internal_value, do: :shared_spec
it "will be overridden" do
expect a() |> to(eq 1)
expect c() |> to(eq 3)
expect e() |> to(eq 5)
end
it "returns defaults" do
expect b() |> to(eq 20)
expect d() |> to(eq nil)
end
it "does not override internal 'lets'" do
expect internal_value() |> to(eq :shared_spec)
end
end
defmodule LetOverridableSpec do
use ESpec, async: true
let :internal_value, do: :some_spec
it_behaves_like(SharedSpec, a: 1, c: 3, e: 5)
end
In case you want to add some "global" shared specs which you want to use in multiple specs, ESpec has you covered. Simply add these files to your spec/shared
folder. The place where mix espec.init
generates you a placeholder folder and file.
By default ESpec loads all files contained in <your_spec_paths>/shared
which match your spec_pattern
.
The Configuration and options chapter contains details on how to control this behaviour.
In case you already use Code.require_file/1
in your spec_helper.exs
don't sweat. ESpec makes sure to require each file only once, it ignores files which already have been included.
Examples can be generated from code "templates". This should help with making the code more DRY:
defmodule GeneratedExamplesSpec do
use ESpec, async: true
subject(24)
Enum.map 2..4, fn(n) ->
it "is divisible by #{n}" do
expect rem(subject(), unquote(n)) |> to(eq 0)
end
end
end
Please mind the unquote
call above - if you forget to unquote
the n
variable the compiler will show some warnings about it missing and eventually stop with an error: undefined function n/0
.
There is an async: true
option you can set for the context or for the individual example:
defmodule AsyncSpec do
use ESpec, async: true
it do: "async example"
context "Sync", async: false do
it do: "sync example"
it "async again", async: true do
"async"
end
end
end
The examples will be partitioned into two queries. Examples in asynchronous query will be executed in parallel in different processes.
Don't use async: true
if you change the global state in your specs!
expect actual |> to(eq expected) # passes if actual == expected
expect actual |> to(eql expected) # passes if actual === expected
expect actual |> to(be_close_to expected, delta)
expect actual |> to(be_between hard_place, rock)
Can be used with :>
, :<
, :>=
, :<=
, and etc.
expect actual |> to(be operator, value)
Passes if apply(Kernel, operator, [actual, value]) == true
expect actual |> to(match_pattern {:ok, _}) # {:ok, _} = actual
It's not possible to call functions in the pattern and use the return value as
pattern ({:ok, function()}
), this obviously means no let
functions. If you
neeed to use the return value of a function, use a variable:
value = function()
expect actual |> to(match_pattern {:ok, ^value})
expect actual |> to(be_true())
expect actual |> to(be_truthy())
expect actual |> to(be_false())
expect actual |> to(be_falsy())
expect actual |> to(match ~r/expression/)
expect actual |> to(match "string")
There are many helpers to test enumerable collections:
expect collection |> to(be_empty()) # Enum.count(collection) == 0
... have value # Enum.member?(collection, value)
... have_all func # Enum.all?(collection, func)
... have_any func # Enum.any?(collection, func)
... have_at position, value # Enum.at?(collection, position) == value
... have_count value # Enum.count(collection) == value
... have_size value # alias
... have_length value # alias
... have_count_by func, value # Enum.count(collection, func) == value
... have_max value # Enum.max(collection) == value
... have_max_by func, value # Enum.max_by(collection, fun) == value
... have_min value # Enum.min(collection) == value
... have_min_by func, value # Enum.min_by(collection, fun) == value
expect list |> to(have_first value) # List.first(list) == value
... have_last value # List.last(list) == value
... have_hd value # hd(list) == value
... have_tl value # tl(list) == value
... contain_exactly value # Keyword.equals?(list, value)
... match_list value # alias for contain_exactly
expect binary |> to(have_byte_size value) # byte_size(binary) == value
expect string |> to(have_first value) # String.first(string) == value
... have_last value # String.last(string) == value
... start_with value # String.starts_with?(string, value)
... end_with value # String.end_with?(string, value)
... have value # String.contains?(string, value)
... have_at pos, value # String.at(string, pos) == value
... have_length value # String.length(string) == value
... have_size value # alias
... have_count value # alias
... be_valid_string() # String.valid?(string)
... be_printable() # String.printable?(string)
... be_blank() # String.length(string) == 0
... be_empty() # String.length(string) == 0
expect map |> to(have foo: "bar") # Map.get(map, :foo) == "bar"
expect map |> to(have {:foo, "bar"}) # Map.get(map, :foo) == "bar"
expect map |> to(have {"foo", "bar"}) # Map.get(map, :foo) == "bar"
expect map |> to(have_key value) # Map.has_key?(map, value)
expect map |> to(have_value value) # Enum.member?(Map.values(map), value)
expect pid |> to(be_alive) # Process.alive?(pid)
have
also works for Structs.
expect :espec |> to(be_atom) #is_atom(:espec) == true
... be_binary()
... be_bitstring()
... be_boolean()
... be_integer()
... be_float()
... be_number()
... ...
... ...
... be_tuple()
... be_list()
... be_map()
... be_port()
... be_pid()
... be_reference()
... be_function()
... be_function arity
... be_struct()
... be_struct StructExample
expect function |> to(raise_exception())
expect function |> to(raise_exception ErrorModule)
expect function |> to(raise_exception ErrorModule, "message")
expect function |> to(throw_term())
expect function |> to(throw_term term)
Test if call of function1 change the function2 returned value to smth or from to smth
expect function1 |> to(change function2)
expect function1 |> to(change function2, to)
expect function1 |> to(change function2, from, to)
expect function1 |> to(change function2, by: value)
Test if function returns {:ok, result}
or {:error, reason}
tuple
expect {:ok, :the_result} |> to(be_ok_result())
expect {:error, :an_error} |> to(be_error_result())
assert
and refute
If you are missing ExUnit assert
and refute
, ESpec has such functions as aliases to be_truthy
and be_falsy
defmodule AssertAndRefuteSpec do
use ESpec
it "asserts" do
assert "ESpec"
#expect "ESpec" |> to(be_truthy())
end
it "refutes" do
refute nil
#expect nil |> to(be_falsy())
end
end
assert_receive
and refute_receive
assert_receive
(assert_received
) and refute_receive
(refute_received) work identically to ExUnit ones.
assert_receive
asserts that a message matching pattern was or is going to be received within timeout.
assert_received
asserts that a message was received and is in the current process mailbox. It is the same as assert_receive
with 0 timeout.
refute_receive
asserts that a message matching pattern was not received and won’t be received within the timeout.
refute_received
asserts that a message was not received (refute_receive
with 0 timeout).
The default timeout for assert_receive
and refute_receive
is 100ms. You can pass custom timeout as a second argument.
defmodule AssertReceviveSpec do
use ESpec
it "demonstrates assert_received" do
send(self(), :hello)
assert_received :hello
end
it "demonstrates assert_receive with custom timeout" do
parent = self()
spawn(fn -> :timer.sleep(200); send(parent, :hello) end)
assert_receive(:hello, 300)
end
it "demonstrates refute_receive" do
send(self(), :another_hello)
refute_receive :hello_refute
end
end
capture_io
and capture_log
capture_io
and capture_log
are just copied from ExUnit and designed to test IO or Logger output:
defmodule CaptureSpec do
use ESpec
it "tests capture_io" do
message = capture_io(fn -> IO.write "john" end)
message |> should(eq "john")
end
it "tests capture_log" do
message = capture_log(fn -> Logger.error "log msg" end)
expect message |> to(match "log msg")
end
end
You can define your own matchers!
The only functions you should implement is match/2
, success_message/4
, and error_message
.
Read the wiki page for detailed instructions.
There is an example in custom_assertion_spec.exs.
There are community supported projects with sets of mathers:
If you keep the naming convention 'module TheModuleSpec is spec for TheModule' you can access tested module by described_module()
helper.
defmodule TheModule do
def fun, do: :fun
end
defmodule TheModuleSpec do
use ESpec
it do: expect described_module().fun |> to(eq :fun)
end
ESpec uses Meck to mock functions. You can mock the module with 'allow accept':
defmodule MocksSpec do
use ESpec
context "with old syntax" do
before do: allow SomeModule |> to(accept(:func, fn(a, b) -> a + b end))
it do: expect SomeModule.func(1, 2) |> to(eq 3)
end
context "with new syntax" do
before do: allow SomeModule |> to(accept :func, fn(a, b) -> a + b end)
it do: expect SomeModule.func(1, 2) |> to(eq 3)
end
end
If you don't specify the function to return ESpec creates stubs with arity 0
and 1
:
fn -> end
and fn(_) -> end
, which return nil
.
defmodule DefaultMocksSpec do
use ESpec
before do: allow SomeModule |> to(accept :func)
it do: expect SomeModule.func |> to(be_nil())
it do: expect SomeModule.func(42) |> to(be_nil())
end
You can also use pattern matching in your mocks:
defmodule PatternMockSpec do
use ESpec
before do
args = {:some, :args}
allow SomeModule |> to(accept :func, fn(^args) -> {:ok, :success} end)
end
it do: expect SomeModule.func({:some, :args}) |> to(be_ok_result())
it "raises exception when does not match" do
expect(fn -> SomeModule.func({:wrong, :args}) end)
|> to(raise_exception FunctionClauseError)
end
end
Behind the scenes 'allow accept' makes the following:
:meck.new(module, [:non_strict, :passthrough])
:meck.expect(module, name, function)
Find the explanation about the :non_strict
and :passthrough
options here.
The default options ([:non_strict, :passthrough]
) can be overridden:
allow SomeModule |> to(accept :func, fn(a,b) -> a + b end, [:non_strict, :unstick])
All the mocked modules are unloaded with :meck.unload(modules)
after each example.
You can also pass a list of atom-function pairs to the accept
function:
allow SomeModule |> to(accept f1: fn -> :f1 end, f2: fn -> :f2 end)
One can use passthrough/1
function to call the original function:
before do
allow SomeModule |> to(accept(:fun, fn
:mocked -> "mock!"
_ -> passthrough([args])
end))
end
it do: expect SomeModule.fun(:mocked) |> to(eq "mock!")
it do: expect SomeModule.fun(2) |> to(eq 3)
The passthrough/1
just calls the :meck.passthrough/1
from the :meck
module.
There is also an expectation to check if the module accepted a function call:
accepted(func, args \\ :any, opts \\ [pid: :any, count: :any])
So, the options are:
any
;pid
of the process which called the function;defmodule MockOptionsSpec do
use ESpec
before do
allow SomeModule |> to(accept :func, fn(a,b) -> a + b end)
SomeModule.func(1, 2)
end
it do: expect SomeModule |> to(accepted :func)
it do: expect SomeModule |> to(accepted :func, [1,2])
describe "with options" do
defmodule Server do
def call(a, b) do
ESpec.SomeModule.func(a, b)
ESpec.SomeModule.func(a, b)
end
end
before do
pid = spawn(Server, :call, [1, 2])
:timer.sleep(100)
{:ok, pid: pid}
end
it do: expect ESpec.SomeModule |> to(accepted :func, [1,2], pid: shared.pid, count: 2)
end
end
accepted
assertion checks :meck.history(SomeModule)
. See meck documentation.
Don't use async: true
when using mocks!
ESpec has comparison support for Elixir's date(time) related structs. Specifically, it has support for Date, Time, NaiveDateTime, and DateTime structs using ESpec's be_close_to
and be
assertions. It allows you to compare using the lowest-level granularity available in the struct. For example, since the lowest level of granularity available in a NaiveDateTime is the microsecond, you can compare how close to NaiveDateTime structs are with respect to microseconds.
For the be
assertions, ESpec supports a syntax with a granularity tuple (or keyword list) or a syntax without it. The following examples are shown with a Date struct.
it do: expect ~D[2020-08-07] |> to(be :>=, ~D[2017-08-07])
it do: expect ~D[2020-08-07] |> to(be :>=, ~D[2017-08-07], {:years, 3})
# or alternatively, you can do:
it do: expect ~D[2020-08-07]) |> to(be :>=, ~D[2017-08-07], years: 3)
expect ~D[2017-08-07] |> to(be_close_to(~D[2018-08-07], {:years, 1}))
# or alternatively, you can do:
it do: expect ~D[2017-08-07] |> to(be_close_to(~D[2020-08-07], {:years, 3}))
expect ~N[2017-08-07 01:10:10.000001] |> to(be_close_to(~N[2017-08-07 01:10:10.000003], {:microseconds, 2}))
# or alternatively, you can do:
it do: expect ~N[2017-08-07 01:10:10.000001] |> to(be_close_to(~N[2017-08-07 01:10:10.000003], {:microseconds, 2}))
expect ~T[01:10:10] |> to(be_close_to(~T[01:50:10], {:minutes, 40}))
# or alternatively, you can do:
it do: expect ~T[01:10:10] |> to(be_close_to(~T[01:50:10], {:minutes, 40}))
Note the example shows a DateTime comparison with utc and std offsets for time zone differences. It is up to the user to be aware of the time zone utc and std offsets.
context "Success with DateTime with utc and std offsets to represent time zone differences" do
let :datetime_pst, do: %DateTime{year: 2017, month: 3, day: 15, hour: 1, minute: 30, second: 30, microsecond: {1, 6}, std_offset: 1*3600, utc_offset: -8*3600, zone_abbr: "PST", time_zone: "America/Los_Angeles"}
let :datetime_est, do: %DateTime{year: 2017, month: 3, day: 15, hour: 6, minute: 30, second: 30, microsecond: {1, 6}, std_offset: 1*3600, utc_offset: -5*3600, zone_abbr: "EST", time_zone: "America/New_York"}
it do: expect datetime_pst() |> to(be_close_to(datetime_est(), {:hours, 2}))
end
Meck has trouble mocking certain modules, such as erlang
, os
, and timer
.
Also, meck does not track module-local calls. For example, this will not be tracked:
defmodule SomeModule
def some_func, do: another_func()
def another_func, do: nil
end
But this will:
defmodule SomeModule
def some_func, do: __MODULE__.another_func()
def another_func, do: nil
end
It is recommended to prefix module-local calls with __MODULE__
when using them with meck.
See this section in the meck README for a more detailed explanation.
ESpec has functionality similar to ExUnit.DocTest
.
Read more about docs syntax here
The functionality is implemented by two modules:
ESpec.DocExample
parses module documentation and ESpec.DocTest
creates 'spec' examples for it.
ESpec.DocExample
functions are just copy-pasted from ExUnit.Doctest
parsing functionality.
ESpec.DocTest
implements doctest
macro which is identical to ExUnit
.
defmodule DoctestSpec do
use ESpec
doctest MySuperModule
end
There are three options (similar to ExUnit.DocTest
):
:except
- generate specs for all functions except those listed (list of {function, arity} tuples).
defmodule DoctestOptionsSpec do
use ESpec
doctest MySuperModule, except: [fun: 1, func: 2]
end
:only
— generate specs only for functions listed (list of {function, arity} tuples).
And :import
to test a function defined in the module without referring to the module name.Default is false
. Use this option with care because you can clash with other modules.
There are three types of specs can be generated based on docs.
@doc """
iex> Enum.map [1, 2, 3], fn(x) ->
...> x * 2
...> end
[2,4,6]
"""
Such examples will be converted to:
it "Example description" do
expect input |> to(eq output)
end
#Name<...>.
:@doc """
iex> Enum.into([a: 10, b: 20], HashDict.new)
#HashDict<[b: 20, a: 10]>
"""
The examples will be converted to:
it "Example description" do
expect inspect(input) |> to(eq output)
end
@doc """
iex(1)> String.to_atom((fn() -> 1 end).())
** (ArgumentError) argument error
"""
The examples will be tested as:
it "Example description" do
expect fn -> input end |> to(raise_exception error_module, error_message)
end
`MIX_ENV=test mix help espec`
You can change (in the mix.exs
file) the folder where your specs are and the pattern to match these files.
def project do
...
spec_paths: ["my_specs", "espec"],
spec_pattern: "*_espec.exs",
...
end
In addition to specifying the spec paths you can also tell ESpec where to find your SharedSpecs.
def project do
...
shared_spec_paths: ["my_specs/shared", "espec/my_shared"],
shared_spec_pattern: "*_shared.exs",
...
end
One can run specs with coverage:
mix espec --cover
Find the results in /cover
folder.
ESpec, like ExUnit, uses very simple wrapper around OTP's cover. But you can override this.
Take a look to coverex as a perfect example.
Similar to ExUnit, the --stale
command line option attempts to run only those test files which reference modules that have changed since the last time you ran this task with --stale
.
Running the whole test suite:
mix espec --stale
Running individual file(s):
mix espec spec/assertions/be_spec.exs spec/assertions/be_close_to_spec.exs --stale
There are three formatters in ESpec:
The Doc formatter tries to help you read the failed tests results by doing diffs between the expected and actual values in some cases (the eq and eql assertions, for example). If you don't want this you can disable it like this:
ESpec.configure fn(config) ->
config.formatters [
{ESpec.Formatters.Doc, %{diff_enabled?: false}}
]
end
By default ESpec uses 'Doc' with empty options. In order to use another one, you must specify formatters in 'ESpec.config'
For example:
ESpec.configure fn(config) ->
config.formatters [
{ESpec.Formatters.Json, %{out_path: "results.json"}},
{ESpec.Formatters.Html, %{out_path: "results.html"}},
{ESpec.YouCustomFormatter, %{a: 1, b: 2}},
]
end
ESpec design allows custom formatters of test results.
The custom formatter is a module which use ESpec.Formatters.Base
and implement 3 functions:
init/1
handle_cast/2
for example_finished
eventhandle_cast/2
for final_result
eventTake a look at lib/espec/formatters
and spec_formatters
folders to see examples
There are community supported formatters:
let_ok
and let_error
before
to ignore not enumerablesbe
and be_close_to
assertions for Date
, Time
, NaiveDateTime
, and DateTime
have
and eq
assertionslet
caching for shared examplesmeck
dependencyRequest a new feature by creating an issue.
Create a pull request with new features or fixes.
ESpec is tested using ExUnit and ESpec. So run:
mix test
mix espec