{"id":14969318,"url":"https://github.com/cabbage-ex/cabbage","last_synced_at":"2025-05-15T11:00:16.046Z","repository":{"id":47126164,"uuid":"77541239","full_name":"cabbage-ex/cabbage","owner":"cabbage-ex","description":"Story BDD tool for executing elixir in ExUnit","archived":false,"fork":false,"pushed_at":"2025-03-14T06:22:38.000Z","size":233,"stargazers_count":150,"open_issues_count":18,"forks_count":34,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-05-12T22:13:58.900Z","etag":null,"topics":["cabbage","cucumber","elixir","exunit","gherkin","scenario"],"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/cabbage-ex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-28T15:08:22.000Z","updated_at":"2025-05-10T22:59:58.000Z","dependencies_parsed_at":"2024-06-19T02:42:35.659Z","dependency_job_id":"4bbb3be5-0b21-4117-ba56-125efe90c8ec","html_url":"https://github.com/cabbage-ex/cabbage","commit_stats":{"total_commits":193,"total_committers":22,"mean_commits":8.772727272727273,"dds":0.7098445595854923,"last_synced_commit":"9fabbf1ae49df603c6317f3d73edb0efeee78d5e"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cabbage-ex%2Fcabbage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cabbage-ex%2Fcabbage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cabbage-ex%2Fcabbage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cabbage-ex%2Fcabbage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cabbage-ex","download_url":"https://codeload.github.com/cabbage-ex/cabbage/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328384,"owners_count":22052632,"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":["cabbage","cucumber","elixir","exunit","gherkin","scenario"],"created_at":"2024-09-24T13:41:35.999Z","updated_at":"2025-05-15T11:00:16.022Z","avatar_url":"https://github.com/cabbage-ex.png","language":"Elixir","readme":"# Cabbage\n\n[![Coverage Status](https://coveralls.io/repos/github/cabbage-ex/cabbage/badge.svg?branch=master)](https://coveralls.io/github/cabbage-ex/cabbage?branch=master)\n[![CircleCI](https://circleci.com/gh/cabbage-ex/cabbage.svg?style=svg)](https://circleci.com/gh/cabbage-ex/cabbage)\n[![Hex.pm](https://img.shields.io/hexpm/v/cabbage.svg)]()\n\n\u003cimg src=\"https://www.organicfacts.net/wp-content/uploads/2013/12/redcabbage.jpg\" width=\"240px\" height=\"180px\"\u003e\u003c/img\u003e\n##### (Looking contribution for a better icon!)\n\nA simple addon on top of [ExUnit](https://hexdocs.pm/ex_unit/ExUnit.html) which provides compile time translation of `.feature` files to exunit tests. Big thanks to [@meadsteve](https://github.com/meadsteve) and the [White Bread](https://github.com/meadsteve/white-bread) project for a huge head start on this project.\n\n## Installation\n\n[Available in Hex](https://hex.pm/packages/cabbage), the package can be installed as:\n\n  1. Add `cabbage` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [{:cabbage, \"~\u003e 0.4.0\"}]\nend\n```\n\n## Example Usage\n\nBy default, feature files are expected inside `test/features`. This can be configured within your application with the following:\n\n```elixir\nconfig :cabbage, features: \"some/other/path/from/your/project/root\"\n```\n\nInside `test/features/coffee.feature` you might have something like:\n\n```gherkin\nFeature: Serve coffee\n  Coffee should not be served until paid for\n  Coffee should not be served until the button has been pressed\n  If there is no coffee left then money should be refunded\n\n  Scenario: Buy last coffee\n    Given there are 1 coffees left in the machine\n    And I have deposited £1\n    When I press the coffee button\n    Then I should be served a coffee\n```\n\nTo translate this to a simple exunit test, all you need to do is provide the translation of lines to steps in the test. Inside `test/features/coffee_test.exs` (or anywhere you like really).\n\n```elixir\ndefmodule MyApp.Features.CoffeeTest do\n  # Options, other than file:, are passed directly to `ExUnit`\n  use Cabbage.Feature, async: false, file: \"coffee.feature\"\n\n  # `setup_all/1` provides a callback for doing something before the entire suite runs\n  # As below, `setup/1` provides means of doing something prior to each scenario\n  setup do\n    on_exit fn -\u003e # Do something when the scenario is done\n      IO.puts \"Scenario completed, cleanup stuff\"\n    end\n    %{my_starting: :state, user: %User{}} # Return some beginning state\n  end\n\n  # All `defgiven/4`, `defwhen/4` and `defthen/4` takes a regex, matched data, state and lastly a block\n  defgiven ~r/^there (is|are) (?\u003cnumber\u003e\\d+) coffee(s) left in the machine$/, %{number: number}, %{user: user} do\n    # `{:ok, state}` gets returned from each callback which updates the state or\n    # leaves the state unchanged when something else is returned\n    {:ok, %{machine: Machine.put_coffee(Machine.new, number)}}\n  end\n\n  defgiven ~r/^I have deposited £(?\u003cnumber\u003e\\d+)$/, %{number: number}, %{user: user, machine: machine} do\n    {:ok, %{machine: Machine.deposit(machine, user, number)}} # State is automatically merged so this won't erase `user`\n  end\n\n  # With no matches, the map is empty. Since state is unchanged, its not necessary to return it\n  defwhen ~r/^I press the coffee button$/, _, state do\n    Machine.press_coffee(state.machine) # instead would be some `hound` or `wallaby` dsl\n  end\n\n  # Since state is unchanged, its not necessary to return it\n  defthen ~r/^I should be served a coffee$/, _, state do\n    assert %Coffee{} = Machine.take_drink(state.machine) # Make your `assert`ions in `defthen/4`s\n  end\nend\n```\n\nThe resulting compiled test will be logically equivalent to:\n\n```elixir\ndefmodule MyApp.Features.CoffeeTest do\n  use ExUnit.Case, async: false\n\n  setup do\n    on_exit fn -\u003e\n      IO.puts \"Scenario completed, cleanup stuff\"\n    end\n    {:ok, %{my_starting: :state, user: %User{}}}\n  end\n\n  # Each scenario would generate a single test case\n  @tag :integration\n  test \"Buy last coffee\", %{my_starting: :state, user: user} do\n    # From the given\n    state = %{user: user, machine: Machine.put_coffee(Machine.new, number)}\n    # From the and\n    state = Map.put(state, :machine, Machine.deposit(machine, user, number))\n    # From the when\n    Machine.press_coffee(state.machine)\n    # From the then\n    assert %Coffee{} = Machine.take_drink(state.machine)\n  end\nend\n```\n\nThis provides the best of both worlds. Feature files for non-technical users, and an actual test file written in Elixir for developers that have to maintain them.\n\n### Tables \u0026 Doc Strings\n\nUsing tables and Doc Strings can be done easily, they are provided through the variables under the names `:table` and `:doc_string`. An example can be seen in [test/data_tables_test.exs](test/data_tables_test.exs) and [test/features/data_tables.feature](test/features/data_tables.feature).\n\n### Running specific tests\n\nTypically to run an ExUnit test you would do something like `mix test test/some_test.exs:12` and elixir will automatically load  `test/some_test.exs` for you, but only run the test on line `12`. Since the feature files are being translated into ExUnit at compile time, you'll have to specify the `.exs` file and not the `.feature` file to run. The line numbers are printed out as each test runs (at the `:info` level, so you may need to increase your logger config if you dont see anything). An example is like as follows:\n\n    # Runs scenario of test/features/coffee.feature on line 13\n    mix test test/feature_test.exs:13\n\n# Developing\n\n## Using Docker Compose\n\nA `docker-compose.yml` is provided for running the tests in containers.\n\n```shell\n$ docker-compose up\n```\n\nTo wipe all `_build` and `deps` you can run:\n```shell\n$ docker-compose down -v\n```\n\nIf you want to interactive, using standard `mix` commands, such as updating dependencies:\n\n```shell\n$ docker-compose run --rm test deps.update --all\n```\n\nOr, if you want to run a single test, that can be accomplished with:\n\n```shell\n$ docker-compose run --rm cabbage test test/feature_test.exs\n```\n\n# Roadmap\n\n- [x] Scenarios\n- [x] Scenario Outlines\n- [x] ExUnit Case Templates\n- [x] Data tables\n- [x] Executing specific tests\n- [x] Tags implementation\n- [ ] Background steps\n- [ ] Integration Helpers for Wallaby (separate project?)\n- [ ] Integration Helpers for Hound (separate project?)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcabbage-ex%2Fcabbage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcabbage-ex%2Fcabbage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcabbage-ex%2Fcabbage/lists"}