README.md

# ESpecPhoenix
[![Build Status](https://travis-ci.org/antonmi/espec.svg?branch=master)](https://travis-ci.org/antonmi/espec_phoenix)

##### ESpec helpers and matchers for Phoenix web framework.
Read about ESpec [here](https://github.com/antonmi/espec).

Read the Paulo A Pereira [post](https://onfido.com/blog/elixir-bdd/).

See [test_app/spec](https://github.com/antonmi/espec_phoenix/tree/master/test_app/spec) for usage examples.

## Contents
- [Installation](#installation)
- [Model specs](#model-specs)
- [Controller specs](#controller-specs)
- [View specs](#view-specs)
- [Requests specs](#requests-specs)
- [Contributing](#contributing)

## Installation

Add `espec_phoenix` to dependencies in the `mix.exs` file:

```elixir
def deps do
  ...
  {:espec_phoenix, "~> 0.3.0", only: :test, app: false},
  #{:espec_phoenix, github: "antonmi/espec_phoenix", only: :test, app: false}, to get the latest version
  ...
end
```
```sh
mix deps.get
```
Set `preferred_cli_env` for `espec` in the `mix.exs` file:

```elixir
def project do
  ...
  preferred_cli_env: [espec: :test],
  ...
end
```
Run:
```sh
MIX_ENV=test mix espec.init
```
The task creates `spec/spec_helper.exs` and `spec/example_spec.exs`.
Run:
```sh
MIX_ENV=test mix espec_phoenix.init
```
The task creates `phoenix_helper.exs`, `espec_phoenix_extend.ex`, and basic specs folders and places simple examples there.

`phoenix_helper.exs` has Phoenix related configurations.
Replace `App.Repo` with your repo module.

You must require this helper in your `spec_helper.exs`:
```elixir
Code.require_file("spec/phoenix_helper.exs")
```
Also you need to checkout your `Ecto` sandbox mode before each example and checkin it after. So `spec_helper.exs` should look like:
```elixir
#require phoenix_helper.exs
Code.require_file("#{__DIR__}/phoenix_helper.exs")

ESpec.start

ESpec.configure fn(config) ->
  config.before fn ->
    Ecto.Adapters.SQL.Sandbox.checkout(App.Repo, [])
  end

  config.finally fn(_shared) ->
    Ecto.Adapters.SQL.Sandbox.checkin(App.Repo, [])
  end
end
```
The `espec_phoenix_extend.ex` file contains `ESpec.Phoenix.Extend` module.
Use this module to import or alias additional modules to your specs.

## Model specs
### Example
```elixir
defmodule App.UserSpec do
  use ESpec.Phoenix, model: App.User
  alias App.User

  let :valid_attrs, do: %{age: 42, name: "some content"}
  let :invalid_attrs, do: %{}

  context "valid changeset" do
    subject do: User.changeset(%User{}, valid_attrs)
    it do: should be_valid
  end
end
```
#### Changeset helpers
```elixir
expect changeset |> to be_valid
... have_errors :name
... have_errors [:name, :surname]
... have_errors name: "can't be blank", surname: "can't be blank"

```

## Controller specs
There is the `action/2` helper function wich call controller functions directy.
### Example
```elixir
defmodule App.UserControllerSpec do
  use ESpec.Phoenix, controller: App.UserController
  alias App.User

  describe "show" do
    let :user, do: %User{id: 1, age: 25, name: "Jim"}

    before do
      allow Repo |> to accept(:get, fn
        User, 1 -> user
        User, id -> passthrough([id])
      end)
    end

    subject do: action(:show, %{"id" => 1})

    it do: should be_successful
    it do: should render_template("show.html")
    it do: should have_in_assigns(user: user)
  end
end
```

#### Conn helpers
##### Check status
```elixir
expect res_conn |> to be_successful  #or be_success
... be_redirection                   #be_redirect
... be_not_found                     #be_missing
... be_server_error                  #be_error

... have_http_status code
... redirect_to user_path(conn, :index)
```
##### Check template and view
```elixir
expect res_conn |> to(render_template "index.html")
... use_view App.UserView
```
##### Check assigns
```elixir
expect res_conn |> to(have_in_assigns :users)
... have_in_assigns [:users, :options]
... have_in_assigns users: users, options: options
```
##### Check flash
```elixir
expect res_conn |> to(have_in_flash :info)
... have_in_flash info: "User created successfully."
```
## View specs
There is the `render/2` helper function available in the view specs.
### Example
```elixir
defmodule App.UserViewsSpec do
  use ESpec.Phoenix, view: App.UserView
  alias App.User

  describe "show" do
    let :user, do: %User{id: 1, age: 25, name: "Jim"}
    subject do: render("show.html", user: user)

    it do: should have_text("Show user")
    it do: should have_text_in("ul li", user.name)
    it do: should have_text_in("ul li", user.age)
    it do: should have_attribute_in("a", href: user_path(conn, :index))
  end
end
```
ESpec.Phoenix uses [Floki](https://github.com/philss/floki) to parse html.
There are some mathers for html string or for `conn` structure.
#### Content helpers
##### Check presence of plain text
```elixir
expect html |> to(have_text "some text")    #String.contains?(html, "some text")
... have_content "some text"
```
##### Check presence of some selector
```elixir
expect html |> to(have_selector "input #user_name")   #Floki.find(html, "input #user_name")
```

##### Check text in the selector
```elixir
expect html |> to(have_text_in "label", "Name")
```
##### Check attributes in the selector
```elixir
expect html |> to(have_attributes_in "form", action: "/users", method: "post")
```
## Requests specs
Requests specs tests request/response cycles from end to end using a black box approach.
Functions for corresponding http methods are imported from `Phoenix.ConnTest`.
Both 'Conn helpers' and 'Content helpers' available.
### Example
```elixir
defmodule App.UserRequestsSpec do
  use ESpec.Phoenix, request: App.Endpoint
  alias App.User

  describe "list user" do
    before do
      {:ok, user1} = %User{name: "Bill", age: 25} |> Repo.insert
      {:ok, user2} = %User{name: "Jonh", age: 26} |> Repo.insert
      {:shared, user1: user1, user2: user2}
    end

    subject! do: get(conn(), user_path(conn(), :index))

    it do: should be_successful

    it "checks content" do
      should have_content shared.user1.name
      should have_content shared.user2.name
    end
  end
end
```
## Contributing
There is a [test_app](https://github.com/antonmi/espec_phoenix/tree/master/test_app) with specs inside.
Run `mix deps.get` in `test_app` folder.
Change database settings in `test_app/config/test.exs`.
Run tests with `mix espec`