A BDD framework for your Elixir projects. It's main goal is to provide a rich, expressive syntax for you to develop your unit tests against. Think of it as RSpec's little Elixir-loving brother.
Pavlov is an abstraction built on top of the excellent ExUnit, Elixir's standard testing library, so all of its standard features are still supported.
Here's a short and sweet example of Pavlov in action:
defmodule OrderSpec do
use Pavlov.Case, async: true
import Pavlov.Syntax.Expect
describe ".sum" do
context "When the Order has items" do
let :order do
%Order{items: [
{"burger", 10.0}
{"fries", 5.2}
]}
end
it "sums the prices of its items" do
expect Order.sum(order) |> to_eq 15.2
end
end
end
end- Usage
- Describe and Context
- Let
- Subject
- Expects syntax
- Included Matchers
- Callbacks
- Mocking
- Skipping tests
- Development
- Contributing
Add Pavlov as a dependency in your mix.exs file:
defp deps do
[{:pavlov, ">= 0.1.0", only: :test}]
endAfter you are done, run mix deps.get in your shell to fetch the dependencies.
To start execution of your Pavlov tests, add the following to your 'test/test_helper.exs':
Pavlov.startAfterwards, running mix test in your shell will run all test suites.
You may use the describe and context constructs to group tests together in a logical way. Although context is just an alias for describe, you may use it to add some extra meaning to your tests, ie. you can use contexts within a described module function to simulate different conditions under which your function should work.
You can use let to define memoized helper methods for your tests. The returning value is cached across all invocations. 'let' is lazily-evaluated, meaning that its body is not evaluated until the first time the method is invoked.
let :order do
%Order{items: [
{"burger", 10.0}
{"fries", 5.2}
]}
endYou can use subject to explicitly define the value that is returned by the subject method in the example scope. A subject declared in a context will be available in child contexts as well.
describe "Array" do
subject do
[1, 2, 3]
end
it "has the prescribed elements" do
assert subject == [1, 2, 3]
end
context "Inner context" do
it "can use an outer-scope subject" do
assert subject == [1, 2, 3]
end
end
endIf you are using Expects syntax together with subject, you can use the is_expected helper:
describe "Numbers" do
subject do: 5
context "straight up, no message" do
it is_expected |> to_eq 5
end
context "you can also use a custom message" do
it "is equal to 5" do
is_expected |> to_eq 5
end
end
endBy default, every created context via describe or context contains a subject that returns nil.
You may use the regular ExUnit assert syntax if you wish, but Pavlov includes
an expect syntax that makes your tests more readable.
If you wish to use this syntax, simply import the Pavlov.Syntax.Expect at the
beginning of your Test module:
defmodule MyTest do
use Pavlov.Case, async: true
import Pavlov.Syntax.Expect
#...
endAll core matchers are supported under both syntaxes.
When using the expects syntax, all matchers have negative counterparts, ie:
expect 1 |> not_to_eq 2
expect(1 > 5) |> not_to_be_trueVisit the Pavlov Wiki to learn more about all of the core matchers available for your tests.
For now, Pavlov only supports callbacks that run before test cases. ExUnit's
on_exit callback is still fully supported though, and may be used normally inside your before callbacks.
Runs the specified code before every test case.
describe "before :each" do
before :each do
IO.puts "A test is about to start"
:ok
end
it "does something" do
#...
end
it "does something else" do
#...
end
endIn this case, "A test is about to start" is printed twice to the console.
Runs the specified code once before any tests run.
describe "before :all" do
before :all do
IO.puts "This suite is about to run"
:ok
end
it "does something" do
#...
end
it "does something else" do
#...
end
endIn this case, "This suite is about to run" is printed once to the console.
Pavlov provides facilities to mock functions in your Elixir modules. This is achieved using Meck, an erlang mocking tool.
Here's a simple example using HTTPotion:
before :each do
allow HTTPotion |> to_receive(get: fn(url) -> "<html></html>" end)
end
it "gets a page" do
result = HTTPotion.get("http://example.com")
expect HTTPotion |> to_have_received :get
expect result |> to_eq "<html></html>"
endIf you want the mock to retain all other functions in the original module,
then you will need to pass the opts List argument to the allow function
and include the :passthrough value. The allow function specifies a default
opts List that includes the :no_link value. This value should be included
in the List as it ensures that the mock (which is linked to the creating
process) will unload automatically when a crash occurs.
before :each do
allow(HTTPotion, [:no_link, :passthrough]) |> to_receive(get: fn(url) -> "<html></html>" end)
endExpectations on mocks also work using asserts syntax via the called matcher:
before :each do
allow HTTPotion |> to_receive(get: fn(url) -> "<html></html>" end)
end
it "gets a page" do
HTTPotion.get("http://example.com")
assert called HTTPotion.get
endYou can also perform assertions on what arguments were passed to a mocked method:
before :each do
allow HTTPotion |> to_receive(get: fn(url) -> "<html></html>" end)
end
it "gets a page" do
HTTPotion.get("http://example.com")
expect HTTPotion |> to_have_received :get |> with_args "http://example.com"
endIn asserts syntax:
before :each do
allow HTTPotion |> to_receive (get: fn(url) -> url end )
end
it "gets a page" do
HTTPotion.get("http://example.com")
assert called HTTPotion.get("http://example.com")
endPavlov runs with the --exclude pending:true configuration by default, which
means that tests tagged with :pending will not be run.
Pavlov offers several convenience methods to skip your tests, BDD style:
Marks a specific test as pending and will not run it.
xit "does not run" do
# This will never run
endMarks a group of tests as pending and will not run them. Just as describe
and context, xdescribe and xcontext are analogous.
xdescribe "A pending group" do
it "does not run" do
# This will never run
end
it "does not run either" do
# This will never run either
end
endAfter cloning the repo, make sure to download all dependencies using mix deps.get.
Pavlov is tested using Pavlov itself, so the general philosophy is to simply write a test using a given feature until it passes.
Simply run mix test
Run MIX_ENV=docs mix docs. The resulting HTML files will be output to the docs folder.
- Fork it ( https://github.com/sproutapp/pavlov/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request