ESpec 
ESpec is a BDD test framework for Elixir.
It is NOT a wrapper around ExUnit but the independent test framework written from scratch.
ESpec is inspired by RSpec and the main idea is to be close to its perfect DSL.
Features
-
Test organization with
describe,context,it, and etc blocks -
Familiar matchers:
eq,be_close_to,raise_exception, etc -
RSpec expectation syntax:
-
With
expecthelper:expect(smth1).to eq(smth2)oris_expected.to eq(smth)whensubjectis defined; -
With old-style
should:smth1 |> should eq smth2orshould eq smthwhensubjectis defined.
-
With
beforeandfinallyblocks (like RSpecbeforeandafter)let,let!andsubject- Mocks with Meck
Contents
- Installation
- Run specs
- Context blocks
- Examples
- 'before' and `'finally'
- 'double-underscore'
- Matchers
- Mocks
- Configuration
## Installation
Add
especto dependencies in themix.exsfile:elixir def deps do ... {:espec, "~> 0.3.5", only: :test} #{:espec, github: "antonmi/espec", only: :test} to get the latest version ... endsh mix deps.getThen run:sh MIX_ENV=test mix espec.initThe task createsspec/spec_helper.exsandspec/example_spec.exs. Setpreferred_cli_envforespecin themix.exsfile:elixir def project do ... preferred_cli_env: [espec: :test] ... endOr run withMIX_ENV=test. Place your_spec.exsfiles intospecfolder.use ESpecin the 'spec module'.elixir defmodule SomeSpec do use ESpec it do: expect(1+1).to eq(2) it do: (1..3) |> should have 2 end## Run specssh mix especRun specific spec:sh mix espec spec/some_spec.exs:25Read the help:sh MIX_ENV=test mix help espec## Context blocks There are three macros with the same functionality:context,describe, andexample_group. Context can have description and options.elixir defmodule SomeSpec 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 endAvailable options are:skip: trueorskip: "Reason"- skips examples in the context;focus: true- sets focus to run with--focusoption. There are alsoxcontext,xdescribe,xexample_groupmacros to skip example groups. Andfcontext,fdescribe,fexample_groupfor focused groups. ## Examplesexample,it, andspecifymacros define the 'spec example'.elixir defmodule SomeSpec do 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" endYou can useskip,pendingorfocusoptions 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.elixir defmodule SomeSpec do use ESpec xit "skip", do: "skipped" focus "Focused", do: "Focused example" it "pending example" pending "it is also pending example" end##beforeandfinallybeforeblocks are evaluated before the example andfinallyruns after the example. The blocks can return{:ok, key: value, ...}, so the keyword list will be saved in the ditionary and can be accessed in otherbeforeblocks, in the example, and infinalyblocks through 'double-undescore'__:elixir defmodule SomeSpec do use ESpec before do: {:ok, a: 1} context "Context" do before do: {:ok, b: __[:a] + 1} finally do: "#{__[:b]} == 2" it do: __.a |> should eq 1 it do: __.b |> should eq 2 finally do: "This finally will not be run. Define 'finally' before the example" end endNote, thatfinallyblocks must be defined before the example. You can configure 'global'beforeandfinallyinspec_helper.exs:elixir ESpec.start ESpec.configure fn(config) -> config.before fn -> {:ok, answer: 42} end #can assign values in dictionary config.finally fn(__) -> __.answer end #can access assigns endThese functions will be called before and after each example which ESpec runs. ## 'double-underscore'__is used to share data between spec blocks. You can access data by__.some_keyor__[:some_key].__.some_keywill raise exception if the key 'somekey' does not exist, while__[:some_key]will returnnil. The__variable appears in yourbefore,finally, inconfig.beforeandconfig.finally, inletandexampleblocks.beforeandfinallyblocks (including 'global') can modify the dictionay when return{:ok, key: value}. The example bellow illustrate the life-cycle of__:spec_helper.exselixir ESpec.start ESpec.configure fn(config) -> config.before fn -> {:ok, answer: 42} end # __ == %{anwser: 42} config.finally fn(__) -> IO.puts __.answer end # it will print 46 endsome_spec.exs:elixir defmodule SomeSpec do use ESpec before do: {:ok, answer: __.answer + 1} # __ == %{anwser: 43} finally do: {:ok, answer: __.answer + 1} # __ == %{anwser: 46} context do before do: {:ok, answer: __.answer + 1} # __ == %{anwser: 43} finally do: {:ok, answer: __.answer + 1} # __ == %{anwser: 45} it do: __.answer |> should eq 44 end endSo, 'config.finally' will print46. Pay attention to howfinallyblocks are defined and evaluated. ##let,let!, andsubjectletandlet!have the same behaviour as in RSpec. Both defines memoizable functions in 'spec module'.letevaluates when accessing the function whilelet!called in 'before' chain. The__is available in 'lets' but neitherletnorlet!can modify the dictionary. ```elixir defmodule SomeSpec do use ESpec before do: {:ok, a: 1} let! :a, do: _.a let :b, do: .a + 1 it do: expect(a).to eq(1) it do: expect(b).to eq(2) end`subject` is just an alias for `let(:subject)`. You can use `is_expected` macro when `subject` is defined.elixir defmodule SomeSpec 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## Matchers #### Equalityelixir expect(actual).to eq(expected) # passes if actual == expected expect(actual).to eql(expected) # passes if actual === expected#### Comparisons Can be used with `:>`, `:<`, `:>=`, `:<=`, and etc.elixir expect(actual).to be operator, valuePasses if `apply(Kernel, operator, [actual, value]) == true` #### Regular expressionselixir expect(actual).to match(~r/expression/) expect(actual).to match("string")#### Enumerable There are many helpers to test enumerable collections:elixir expect(collection).to be_empty #Enum.count(collection) == 0 ... have(value) #Enum.member?(collection, value) ... have_all(fun) #Enum.all?(collection, func) ... have_any(fun) #Enum.any?(collection, func) ... have_at(position, value) #Enum.at?(collection, position) == value ... have_count(value) #Enum.count(collection) == value ... have_count_by(fun, value) #Enum.count(collection, func) == value ... have_max(value) #Enum.max(collection) == value ... have_max_by(fun, value) #Enum.max_by(collection, fun) == value ... have_min(value) #Enum.min(collection) == value ... have_min_by(fun, value) #Enum.min_by(collection, fun) == value#### List specificelixir expect(list).to have_first(value) #List.first(list) == value ... have_last(value) #List.last(list) == value ... have_hd #hd(list) == value ... have_tl #tl(list) == value#### Type checkingelixir expect(:espec).to be_atom #is_atom(:espec) == true ... be_binary ... be_bitstring ... be_boolean ... ... ... ... ... should be_tuple ... be_function ... be_function(arity)#### Exceptionselixir expect(function).to raise_exception expect(function).to raise_exception(ErrorModule) expect(function).to raise_exception(ErrorModule, "message")#### Throwselixir expect(function).to throw_term expect(function).to throw_term(term)#### Change state Test if call of function1 change the function2 returned value to smth or from to smthelexir expect(function1).to change(function2, to) expect(function1).to change(function2, from, to)## Mocks ESpec uses [Meck](https://github.com/eproxus/meck) to mock functions. You can mock the module with 'allow accept':elixir defmodule SomeSpec do use ESpec before do: allow(SomeModule).to accept(:func, fn(a,b) -> a+b end) it do: expect(SomeModule.func(1, 2)).to eq(3) endNote, when you mock some function in module, `meck` creates an absolutely new module. You can also pass a list of atom-function pairs to the `accept` function:elixir allow(SomeModule).to accept(f1: fn -> :f1 end, f2: fn -> :f2 end)There is also an expectation to check if the module accepted a function call:elixir defmodule SomeSpec do use ESpec before do: allow(SomeModule).to accept(:func, fn(a,b) -> a+b end) before do: SomeModule.func(1, 2) it do: expect(SomeModule).to accepted(:func, [1,2]) end``expect(SomeModule).to accepted(:func, [1,2])just checksmeck.history(SomeModule)`. ## Configuration TODO