{"id":13509246,"url":"https://github.com/batate/shouldi","last_synced_at":"2026-02-21T15:05:38.093Z","repository":{"id":22494608,"uuid":"25834026","full_name":"batate/shouldi","owner":"batate","description":"Elixir testing libraries with nested contexts, superior readability, and ease of use","archived":false,"fork":false,"pushed_at":"2016-07-11T14:30:54.000Z","size":73,"stargazers_count":135,"open_issues_count":5,"forks_count":15,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-10-21T18:49:14.091Z","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":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/batate.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}},"created_at":"2014-10-27T18:54:46.000Z","updated_at":"2024-04-07T08:28:51.000Z","dependencies_parsed_at":"2022-08-20T17:11:33.508Z","dependency_job_id":null,"html_url":"https://github.com/batate/shouldi","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/batate/shouldi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/batate%2Fshouldi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/batate%2Fshouldi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/batate%2Fshouldi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/batate%2Fshouldi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/batate","download_url":"https://codeload.github.com/batate/shouldi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/batate%2Fshouldi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29684076,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T14:31:22.911Z","status":"ssl_error","status_checked_at":"2026-02-21T14:31:22.570Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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:05.162Z","updated_at":"2026-02-21T15:05:38.079Z","avatar_url":"https://github.com/batate.png","language":"Elixir","funding_links":[],"categories":["Testing"],"sub_categories":[],"readme":"ShouldI\n=======\n\nExUnit is fine for small, simple applications, but when you want to do more complex test cases, it has limitations. ShouldI provides nested contexts to eliminate duplication in tests, and has better support for naming tests based on behavior. This API is based on the shoulda framework for Ruby on Rails.\n\nQuick start\n------------\nJust add the hex dependency to your mix file:\n\n~~~elixir\ndefp deps do\n  [{:shouldi, only: :test}]\nend\n~~~\n\nand add\n\n~~~elixir\n...\nuse ShouldI\n...\n~~~\n\nto your test script in place of\n\n~~~elixir\n...\nuse ExUnit.Case\n...\n~~~\n\nName tests with `should`\n------------\nWhen you're testing behavior, you can get better names with a more descriptive macro. The test code...\n\n~~~elixir\ntest \"should return ok on parse\" do\n  assert :ok == Parser.parse\nend\n~~~\n\n...can become more descriptive and shorter with...\n\n\n~~~elixir\nshould \"return :ok on parse\" do\n   assert :ok == Parser.parse\nend\n~~~\n\nNest your context using `having`\n---------------\n\nSay you have a test case that needs some setup. ExUnit has support for a context that can be set once, and passed to all clients. You can use the `setup` method to pass a map to each of your test cases, like this:\n\n~~~elixir\ndefmodule MyFlatTest do\n  setup context do\n    {:ok, Dict.put context, :necessary_key, :necessary_value}\n  end\n\n  test( \"this test needs :necessary_key\", context ) do\n    assert context.necessary_key == :necessary_value\n  end\nend\n~~~\n\nThis approach breaks down when several, but not all, tests need the same set of values. ShouldI solves this problem with nested contexts, which you can provide with the `having` keyword, like this:\n\n~~~elixir\ndefmodule MyFatTest do\n\n  having \"necessary_key\" do\n    setup context do\n      Dict.put context, :necessary_key, :necessary_value\n    end\n\n    should( \"have necessary key\", context ) do\n      assert context.necessary_key == :necessary_value\n    end\n  end\n\n  having \"sometimes_necessary_key\" do\n    setup context do\n      Dict.put context, :sometimes_necessary_key, :sometimes_necessary_value\n    end\n\n    should( \"have necessary key\", context ) do\n      assert context.sometimes_necessary_key == :sometimes_necessary_value\n    end\n  end\nend\n~~~\n\nThis approach is much nicer than the alternatives when you're testing something like a controller with dramatically different requirements across tests:\n\n~~~elixir\nhaving \"a logged in user\" do\n  setup context do\n    login context, user\n  end\n\n  ...\nend\n\nhaving \"a logged out user\" do\n   ...\nend\n\nhaving \"a logged in admin\" do\n  setup context do\n    login context, admin\n  end\n\n  ...\nend\n~~~\n\nUse `assign` to set the context\n------------\n`assign` is a macro that is syntactic sugar for updating the `context`.\n\n~~~elixir\nsetup context do\n  Dict.put context, :necessary_key, :necessary_value\nend\n~~~\n\nbecomes\n\n~~~elixir\nsetup context do\n  assign context, necessary_key: :necessary_value\nend\n~~~\n\nUse matchers simplify tests\n---------------------------\n\nYou can package macros that write your own tests. Matchers encode common assertion patterns. For example, our plug matchers\n\n~~~elixir\nhaving \"a logged in admin\" do\n  setup context do\n    login context, admin\n  end\n\n  having \"a get to :index\" do\n    setup context do\n      # process get\n    end\n    should_respond_with :success\n    should_match_body_to \"\u003chtml\u003e\"\n  end\nend\n~~~\n\nThe two matchers, `should_respond_with` and `should_match_body_to`, will run in a single test, against the context created in the `setup` function (or setup functions, if you've used multiple contexts). Even if both of these tests fail, you'll see two failures in your output.\n\nCreate your own matchers with `defmatcher`\n------------------------\n\nWe come prepackaged with a set of matchers, but you can code your own as well. The following is the matcher to check for existence of a dictionary key in the context:\n\n~~~elixir\ndefmatcher should_assign_key([{key, value}]) do\n  quote do\n    assert var!(context)[unquote(key)] == unquote(value)\n  end\nend\n~~~\n\nThis macro allows you to build a matcher macro.\n\nWe'll have more information about creating matchers later. In the mean time, you can read through the matchers we've created in the project. Matchers should be stateless, as all matchers within a `having` clause will run to completion, unless there is an `error`, even if a test fails.\n\nExisting Matchers\n-----------------\n\n- Context\n    - `should_assign_key key, value`: assert that the value for `key` in the context is `value`\n    - `should_match_key key, expected`: assert that the value for `key` in the context satisfies the pattern match `expected`\n    - `should_have_key key`: assert that `key` exists in the context\n    - `should_not_have_key key`: assert that `key` does not exist in the context\n- Plug\n    - `should_respond_with expected`: Assert that the value for `context.connection.status` in the context matches a reasonable value for `:success`, `:redirect`, `:bad_request`, `:unauthorized`, `:missing` or `:error`\n    - `should_match_body_to expected`: Assert that the value for `context.resp_body` contains the text `expected`.\n\nUnique IDs\n----------\n\nWhen running tests asynchronously it can be useful to have a way to generate IDs or names that will not conflict with other tests that run concurrently. `uid()` will generate an ID unique for the current test and setup. If it is called again during the same test it will return the same ID. An additional string can be given `uid(\"some string\")` so multiple IDs can be generated during the same test.\n\nOne Experiment, Multiple Measurements\n-------------------------------------\n\nThe philosophy is that experiments go in `setup` and measurements go into matchers. `shouldi` will make sure that the context is passed between them cleanly so that things compose correctly.\n\nWhen you run a `shouldi` test, for each context:\n\n- one `should` test is created, collecting all of the matchers in a `having` clause.\n- one exunit `test` is created for each `should` block\n- for each test\n- - all of the ancestor `setup` functions will fire, from outermost to innermost.\n- - the test will fire\n- - if a test is a matcher test, all of the matchers will run to completion, even if there is a failure, stopping only on errors.\n- - if a test is a `should` block, the first failure will halt the test, as in `ExUnit`.  \n\nHappy testing. Open an issue if there are any matchers you'd like to see. Feedback and pull requests are welcome. Send a pull request if you'd like to contribute.\n\nSpecial thanks to ThoughtBot's shoulda, which formed the foundation for this approach.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbatate%2Fshouldi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbatate%2Fshouldi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbatate%2Fshouldi/lists"}