ESpec Build Status

ESpec is a BDD test framework for Elixir. Inspired by RSpec.

Features

The main idea is to be close to the RSpec DSL.

Installation

Add espec to dependencies in the mix.exs file:

def deps do
  ...
  {:espec, "~> 0.2.0", only: :test}
  ...
end
mix deps.get

Then run:

mix espec.init

The task creates spec/spec_helper.exs and spec/example_spec.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.

Place your _spec.exs files into spec folder. use ESpec in the ‘spec module’.

defmodule SomeSpec do
  use ESpec
  it do: expect(1+1).to eq(2)
end

Run specs

mix espec

Run specific spec:

mix espec spec/some_spec.exs:25

Context blocks

There are three macros with the same functionality: context, describe, and example_group.

Context can have description and options.

defmodule SomeSpec do
  use ESpec
  example_group do
    context "Some context" do
      it do: expect(true).to be true
    end
    describe "Some another context with opts", focus: true do
     it do: expect(1+1).to eq(2)
    end
  end
end

Available options are:

There are also xcontext, xdescribe, xexample_group macros to skip example groups. And fcontext, fdescribe, fexample_group for focused groups.

Examples

example, it, and specify macros define the spec example.

defmodule SomeSpec do
  example do: expect(true).to be true
  it "Test with description" do
    expect(false).to_not be true
  end
  specify "Test with options", [pending: true], do: "pending"
end

You can use skip, pending or focus options to control evaluation. There are also macros:

before and finally

before blocks are evaluated before the example and finally runs 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 other before blocks, in the example, and in finaly blocks through ‘double-undescore’ __:

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: expect(__[:a]).to eq(1)
    it do: expect(__[:b]).to 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.

‘Double-underscore’

__ is used to share data between spec blocks. You can access data by __.some_key or __[:some_key]. __.some_key will raise exception if the key ‘somekey’ does not exist, while `_[:some_key]will returnnil`.

The __ variable appears in your before, finally and example blocks.

before and finally blocks modify the dictionay when return {:ok, key: value}

let, let!, and subject

let and let! have the same behaviour as in RSpec. Both defines memoizable functions in ‘spec module’. let evaluates when accessing the function while let! called in ‘before’ chain. The __ is available in ‘lets’ but neither let nor let! can modify the dictionary.

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) context "with block" do subject do: 2+2 it do: is_expected.to eq(4) end end ``` ## Matchers #### Equality ```elixir 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, value ``` Passes ifapply(Kernel, operator, [actual, value]) == true#### Regular expressions ```elixir expect(actual).to match(~r/expression/) expect(actual).to match("string") ``` #### Exceptions ```elixir expect(function).to raise_exception expect(function).to raise_exception(ErrorModule) expect(function).to raise_exception(ErrorModule, "message") ``` #### Throws ```elixir 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 smth ```elexir 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) end ``` Note, when you mock some function in modulemeckcreate absolutely new module. You can also pass list of atom-function pairs toacceptfunction: ```elixir allow(SomeModule).to accept(f1: fn -> :f1 end, f2: fn -> :f2 end) ``` There is also an expectation to check if module accepted 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 checkmeck.history(SomeModule)`. ## Configuration TODO