{"id":13509175,"url":"https://github.com/antonmi/espec_phoenix","last_synced_at":"2025-04-04T18:09:33.361Z","repository":{"id":31062621,"uuid":"34621443","full_name":"antonmi/espec_phoenix","owner":"antonmi","description":"ESpec for Phoenix web framework.","archived":false,"fork":false,"pushed_at":"2022-05-26T08:08:19.000Z","size":410,"stargazers_count":137,"open_issues_count":8,"forks_count":33,"subscribers_count":10,"default_branch":"main","last_synced_at":"2024-10-13T23:45:40.784Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antonmi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-04-26T16:59:14.000Z","updated_at":"2024-05-08T22:15:52.000Z","dependencies_parsed_at":"2022-08-17T18:05:35.775Z","dependency_job_id":null,"html_url":"https://github.com/antonmi/espec_phoenix","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonmi%2Fespec_phoenix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonmi%2Fespec_phoenix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonmi%2Fespec_phoenix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonmi%2Fespec_phoenix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antonmi","download_url":"https://codeload.github.com/antonmi/espec_phoenix/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247226215,"owners_count":20904465,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T02:01:04.067Z","updated_at":"2025-04-04T18:09:33.344Z","avatar_url":"https://github.com/antonmi.png","language":"Elixir","readme":"# ESpec.Phoenix\n\n[![Build Status](https://travis-ci.org/antonmi/espec_phoenix.svg?branch=master)](https://travis-ci.org/antonmi/espec_phoenix)\n[![Module Version](https://img.shields.io/hexpm/v/espec_phoenix.svg?style=flat-square)](https://hex.pm/packages/espec_phoenix)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg?style=flat-square)](https://hexdocs.pm/espec_phoenix/)\n[![Total Download](https://img.shields.io/hexpm/dt/espec_phoenix.svg?style=flat-square)](https://hex.pm/packages/espec_phoenix)\n[![License](https://img.shields.io/hexpm/l/espec_phoenix.svg?style=flat-square)](https://github.com/antonmi/espec_phoenix/blob/master/LICENSE.md)\n[![Last Updated](https://img.shields.io/github/last-commit/antonmi/espec_phoenix.svg?style=flat-square)](https://github.com/antonmi/espec_phoenix/commits/master)\n\n##### ESpec helpers for Phoenix web framework.\n\nRead about ESpec [here](https://github.com/antonmi/espec).\n\nESpec.Phoenix is a lightweight wrapper around ESpec which brings BDD to Phoenix web framework.\n\nUse ESpec.Phoenix the same way as ExUnit in you Phoenix application.\n\nThere is [rumbrella](https://github.com/antonmi/espec_phoenix/tree/master/rumbrella) project from great [Programming Phoenix](https://pragprog.com/book/phoenix/programming-phoenix) book. One can find a lot of useful examples there!\n\n## Contents\n- [Installation](#installation)\n- [Migration from previous versions](#migration-from-previous-versions)\n- [Model specs](#model-specs)\n- [Controller specs](#controller-specs)\n- [View specs](#view-specs)\n- [Channel specs](#channel-specs)\n- [LiveView specs](#liveview-specs)\n- [Extensions](#extensions)\n- [Contributing](#contributing)\n\n## Installation\n\nAdd `:espec_phoenix` to dependencies in the `mix.exs` file:\n\n```elixir\ndef deps do\n  ...\n  {:espec_phoenix, \"~\u003e 0.8.2\", only: :test},\n  #{:espec_phoenix, github: \"antonmi/espec_phoenix\", only: :test}, to get the latest version\n  ...\nend\n```\n\n```sh\n$ mix deps.get\n```\n\nSet `:preferred_cli_env` for `:espec` in the `mix.exs` file:\n\n```elixir\ndef project do\n  ...\n  preferred_cli_env: [espec: :test],\n  ...\nend\n```\n\nRun:\n\n```sh\n$ MIX_ENV=test mix espec_phoenix.init\n```\n\nThe task creates `spec/spec_helper.exs`, `phoenix_helper.exs` and `espec_phoenix_extend.ex`.\n\nAlso you need to checkout your `Ecto` sandbox mode before each example and checkin it after. So `spec_helper.exs` should look like:\n\n```elixir\n#require phoenix_helper.exs\nCode.require_file(\"#{__DIR__}/phoenix_helper.exs\")\n\nESpec.configure fn(config) -\u003e\n  config.before fn(_tags) -\u003e\n    :ok = Ecto.Adapters.SQL.Sandbox.checkout(YourApp.Repo)\n  end\n\n  config.finally fn(_shared) -\u003e\n    Ecto.Adapters.SQL.Sandbox.checkin(YourApp.Repo, [])\n  end\nend\n```\nThe `espec_phoenix_extend.ex` file contains `ESpec.Phoenix.Extend` module.\nUse this module to import or alias additional modules in your specs.\n\n## Migration from previous versions\n### There is no other matchers and helpers then ESpec and Phoenix have.\nI've decided to remove all the custom assertions for 'changeset', 'conn' and 'content'.\nThe reason is to make specs more explicit like people used to see using ExUnit.\n\nIf you still want to use them, check out the [espec_phoenix_helpers](https://github.com/facto/espec_phoenix_helpers) project.\n\n## Model specs\n\nUse 'model' tag to identify model specs:\n\n```elixir\nuse ESpec.Phoenix, model: YourModel\n```\n\nWhat ESpec.Phoenix does behind the scene is the following:\n\nUses `ModelHelpers`:\n\n```elixir\ndefmodule ModelHelpers do\n  defmacro __using__(_args) do\n    quote do\n      import Ecto\n      import Ecto.Changeset, except: [change: 1, change: 2]\n      import Ecto.Query\n    end\n  end\nend\n```\n\nCalls `ESpec.Phoenix.Extend.model` function extending your spec module.\n\n#### Note! We don't import `change/1` and `change/2` functions from `Ecto.Changeset` because they conflicts with ESpec functions. If you want to use them, call them directly with module prefix (`Ecto.Changeset.change`).\n\n### Model spec example:\n\n```elixir\ndefmodule Rumbl.UserSpec do\n  use ESpec.Phoenix, model: User, async: true\n  alias Rumbl.User\n\n  @valid_attrs %{name: \"A User\", username: \"eva\", password: \"secret\"}\n  @invalid_attrs %{}\n\n  context \"validation\" do\n    it \"checks changeset with valid attributes\" do\n      changeset = User.changeset(%User{}, @valid_attrs)\n      assert changeset.valid?\n    end\n\n    it \"checks changeset with long username\" do\n      attrs = Map.put(@valid_attrs, :username, String.duplicate(\"a\", 30))\n      assert {:username, \"should be at most 20 character(s)\"} in\n             errors_on(%User{}, attrs)\n    end\n  end\nend\n```\n\nIt is a good practice to place specs with side effects (db access) to another module:\n\n```elixir\ndefmodule Rumbl.UserRepoSpec do\n  use ESpec.Phoenix, model: User, async: true\n  alias Rumbl.User\n\n  @valid_attrs %{name: \"A User\", username: \"eva\"}\n\n  describe \"converting unique_constraint on username to error\" do\n    before do: insert_user(username: \"eric\")\n    let :changeset do\n      attrs = Map.put(@valid_attrs, :username, \"eric\")\n      User.changeset(%User{}, attrs)\n    end\n\n    it do: expect(Repo.insert(changeset)).to be_error_result\n\n    context \"when name has been already taken\" do\n      let :new_changeset do\n        {:error, changeset} = Repo.insert(changeset)\n        changeset\n      end\n\n      it \"has error\" do\n        error = {:username, {\"has already been taken\", []}}\n        expect(new_changeset.errors).to have(error)\n      end\n    end\n  end\nend\n```\n\n## Controller specs\n\nController specs are integration tests that tests interactions among all parts of your application.\n\nUse 'controller' tag to identify controller specs:\n\n```elixir\nuse ESpec.Phoenix, controller: YourController\n```\n\nYour module will be extended with `ESpec.Phoenix.ModelHelpers` and also with `ESpec.Phoenix.ControllerHelpers`:\n\n```elixir\ndefmodule ControllerHelpers do\n  defmacro __using__(_args) do\n    quote do\n      import Plug.Conn\n      import Phoenix.ConnTest, except: [conn: 0, build_conn: 0]\n\n      def build_conn, do: Phoenix.ConnTest.build_conn()\n    end\n  end\nend\n```\n\n#### Note! Deprecated Phoenix.ConnTest.conn/0 function is not imported.\n\nBelow is an example of controller specs:\n\n```elixir\ndefmodule Rumbl.VideoControllerSpec do\n  use ESpec.Phoenix, controller: VideoController, async: true\n\n  describe \"with logged user\" do\n    let :user, do: insert_user(username: \"max\")\n    let! :user_video, do: insert_video(user, title: \"funny cats\")\n    let! :other_video, do: insert_video(insert_user(username: \"other\"), title: \"another video\")\n\n    let :response do\n      assign(build_conn, :current_user, user)\n      |\u003e get(video_path(build_conn, :index))\n    end\n\n    it \"lists all user's videos on index\" do\n      expect(html_response(response, 200)).to match(~r/Listing videos/)\n    end\n\n    it \"has user_video title\" do\n      expect(response.resp_body).to have(user_video.title)\n    end\n\n    it \"does not have other_video title\" do\n      expect(response.resp_body).not_to have(other_video.title)\n    end\n  end\nend\n```\n\nPlease note that due to the fact it's integraton tests, you can actually use it without specifying controller:\n\n```elixir\ndefmodule Rumbl.VideoControllerRequestSpec do\n  use ESpec.Phoenix, controller: true\n\n  describe \"with logged user\" do\n    let! :user_video, do: insert_video(user, title: \"funny cats\")\n\n    let :response do\n      build_conn |\u003e get(\"/videos\")\n    end\n\n    it \"lists all user's videos on index\" do\n      expect(response.resp_body).to match(~r/Listing videos/)\n    end\n  end\nend\n```\n\n## View specs\n\nView specs also are extended with `ESpec.Phoenix.ControllerHelpers` and also imports `Phoenix.View`.\n\n### Example\n\n```elixir\ndefmodule Rumbl.VideoViewSpec do\n  use ESpec.Phoenix, async: true, view: VideoView\n\n  let :videos do\n    [%Rumbl.Video{id: \"1\", title: \"dogs\"},\n      %Rumbl.Video{id: \"2\", title: \"cats\"}]\n  end\n\n  describe \"index.html\" do\n    let :content do\n      render_to_string(Rumbl.VideoView, \"index.html\", conn: build_conn, videos: videos)\n    end\n\n    it do: expect(content).to have(\"Listing videos\")\n\n    it \"has video titles\" do\n      for video \u003c- videos do\n        expect(content).to have(video.title)\n      end\n    end\n  end\nend\n```\n\n## Channel specs\n\n```elixir\nuse ESpec.Phoenix, channel: YourChannel\n```\n\nChannel specs uses `Phoenix.ChannelTest` and `ESpec.Phoenix.ModelsHelpers`.\nUse 'model' tag to identify model specs:\n\n### Example\n\n```elixir\ndefmodule Rumbl.Channels.VideoChannelSpec do\n  use ESpec.Phoenix, channel: Rumbl.VideoChannel\n\n  before do\n    Ecto.Adapters.SQL.Sandbox.mode(Rumbl.Repo, {:shared, self()})\n  end\n\n  let! :user, do: insert_user(name: \"Rebecca\")\n  let! :video, do: insert_video(user, title: \"Testing\")\n\n  before do\n    token = Phoenix.Token.sign(@endpoint, \"user socket\", user.id)\n    {:ok, socket} = connect(Rumbl.UserSocket, %{\"token\" =\u003e token})\n\n    {:shared, socket: socket}\n  end\n\n  before do\n    for body \u003c- ~w(one two) do\n      video\n      |\u003e build_assoc(:annotations, %{body: body})\n      |\u003e Repo.insert!()\n    end\n  end\n\n  before do\n    {:ok, reply, socket} = subscribe_and_join(shared[:socket], \"videos:#{video.id}\", %{})\n    {:shared, reply: reply, socket: socket}\n  end\n\n  it do: expect shared[:socket].assigns.video_id |\u003e to(eq video.id)\n  it do: assert %{annotations: [%{body: \"one\"}, %{body: \"two\"}]} = shared[:reply]\nend\n```\n\n## LiveView specs\n\n```elixir\nuse ESpec.Phoenix, live_view: YourLiveView, async: false, pid: self()\n```\n\nLiveView specs uses `Phoenix.LiveViewTest` and `ESpec.Phoenix.ModelsHelpers`.\nUse 'model' tag to identify model specs:\n\n### Example\n\n```elixir\ndefmodule LiveViewEspecWeb.AccountsLiveSpec do\n  use ESpec.Phoenix, live_view: LiveViewEspecWeb.UserLive.Index, async: false, pid: self()\n\n  describe \"GET /accounts\" do\n    it \"displays the page\" do\n      {:ok, page_live, disconnected_html} = live(live_conn(), \"/live/accounts\")\n      expect disconnected_html |\u003e to(match \"Listing Users\")\n      expect render(page_live) |\u003e to(match \"Listing Users\")\n    end\n  end\nend\n```\n\n[live_view_espec repo](https://github.com/karlosmid/live_view_espec)\n\n## Extensions\n\n[espec_phoenix_helpers](https://github.com/facto/espec_phoenix_helpers) - assertions and helpers that used to be part of this project but were extracted out\n[test_that_json_espec](https://github.com/facto/test_that_json_espec) - matchers for testing JSON\n\n## Contributing\n\n##### Contributions are welcome and appreciated!\n\nRequest a new feature by creating an issue.\n\nCreate a pull request with new features or fixes.\n\nTo run specs:\n\n```sh\n$ mix espec\n```\n\nThere is a [rumbl application](https://github.com/antonmi/espec_phoenix/tree/master/rumbl) with specs inside.\nRun `mix deps.get` in `rumbl` folder.\nChange database settings in `test_app/config/test.exs`.\nRun tests with `mix test` and `mix espec`.\n\n## Copyright and License\n\nCopyright (c) 2015 Anton Mishchuk\n\nThis work is free. You can redistribute it and/or modify it under the\nterms of the MIT License. See the [LICENSE.md](./LICENSE.md) file for more details.\n","funding_links":[],"categories":["Testing"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantonmi%2Fespec_phoenix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantonmi%2Fespec_phoenix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantonmi%2Fespec_phoenix/lists"}