{"id":16655333,"url":"https://github.com/ericmj/true_story","last_synced_at":"2025-03-16T23:31:28.037Z","repository":{"id":62430534,"uuid":"57306544","full_name":"ericmj/true_story","owner":"ericmj","description":"Make your tests tell a story","archived":false,"fork":false,"pushed_at":"2017-02-14T19:01:27.000Z","size":23,"stargazers_count":56,"open_issues_count":0,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-14T22:34:44.754Z","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":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericmj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-04-28T14:05:36.000Z","updated_at":"2023-09-01T11:29:05.000Z","dependencies_parsed_at":"2022-11-01T20:19:39.251Z","dependency_job_id":null,"html_url":"https://github.com/ericmj/true_story","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmj%2Ftrue_story","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmj%2Ftrue_story/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmj%2Ftrue_story/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmj%2Ftrue_story/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericmj","download_url":"https://codeload.github.com/ericmj/true_story/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243658123,"owners_count":20326460,"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-10-12T09:52:37.399Z","updated_at":"2025-03-16T23:31:27.714Z","avatar_url":"https://github.com/ericmj.png","language":"Elixir","funding_links":[],"categories":["Uncategorized"],"sub_categories":["Uncategorized"],"readme":"# TrueStory\n\n_Make your tests tell a story._\n\n## Why?\n\nWe've observed that well-written code and a well-structured API tells a good story. Writing single-purpose functions and improving setup composition improves tests. When you get the setup right, tests get simpler and the structure is easier to read and easier to follow. This thin DSL around ExUnit does exactly that.\n\n## Quick Start\n\nTo use TrueStory, just add as a dependency and write your tests.\n\n[Available in Hex](https://hex.pm/packages/true_story), the package can be installed as:\n\n### Add Your Dependencies\n\nAdd `true_story` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [{:true_story, \"~\u003e 0.0.1\", only: :test}]\nend\n```\n\n### Write Tests\n\nFirst, you'll use `ExUnit.Case`, and also use `TrueStory`, like this:\n\n\n```elixir\ndefmodule MyTest do\n  use ExUnit.Case\n  use TrueStory\n\n  # tests go here\n\nend\n```\nNext, you'll write your tests. Everything will compose, with each part of a story modifying a map, or context. To keep things brief, it's idiomatic to call the context `c`.\n\n### Experiments (`story`) and measurements (`verify`)\n\nA TrueStory test has an experiment and measurements. The experiment changes the world, and the measurements evaluate the impact of the experiment. Experiments go in a `story` section and measurements go in a `verify` block.\n\nThis story tests adding to a map. In the `story` block, you'll test\n\n```elixir\nstory \"adding to a map\", c\n  |\u003e Map.put(:key, :value),\nverify do\n  assert c.key == :value\n  refute c.key == :not_value\nend\n```\n\nPlease note: *verify blocks can't be stateful, and they can't mutate the context!* Keeping verify blocks pure allows us to run all verifications for a single test at once.\n\nThat's it. The `story` section has a name and a context pipe. The context pipe is a macro that allows basic piping, but also has some goodies for convenience.\n\n### Building Your Story\n\nYou can write composable functions that transform a test context to build up your experiments, piece by piece, like this:\n\n```elixir\ndefp add_to_map(c, key, value),\n  do: Map.put(c, key, value)\n\nstory \"adding to a map\", c\n  |\u003e add_to_map(:key, :value),\nverify do\n  assert c.key == :value\n  refute c.key == :not_value\nend\n\nstory \"overwriting a key\", c\n  |\u003e add_to_map(:key, :old),\n  |\u003e add_to_map(:key, :new),\nverify do\n  assert c.key == :new\n  refute c.key == :old\nend\n```\n\nMost application tests are built in the setup. Piping together setup functions like this, you can build a growing library of setup functions for your application, and save your setup library in a common module.\n\n### Linking Multiple Stories\n\nMaybe we would like to measure intermediate steps. To do so, you can run an integration test across tests, like this:\n\n```elixir\nintegrate \"adding multiple keys\" do\n  story \"adding to a map\", c\n    |\u003e add_to_map(:key, :old),\n  verify do\n    assert c.key == :old\n  end\n\n  story \"overwriting a key\", c\n    |\u003e add_to_map(:key, :new),\n  verify do\n    assert c.key == :new\n  end\n\n  story \"overwriting a key\", c\n    |\u003e remove_from_map(:key),\n  verify do\n    refute c.key\n  end\nend\n```\nThis test expands to a single ExUnit test, so there's no concern about compatibility.\n\nLike the experiment steps, these stories compose, with the previous story piped into the next.\n\n## Goodies for convenience\n\n### The `story` pipe\n\nThe pipe operator in the `story` macro allows you to access any key in the context placed there by an earlier pipe segment. For example, say you had some setup functions:\n\n```elixir\ndefp create_user(c),\n  do: Map.put(c, :user, %User{ name: \"Bubba\" }\n\ndefp create_blog(c, user),\n  do: Map.put(c, :blog, %Blog{ name: \"Fishin'\", user: user }\n\ndefp create_post(c, blog, options), do: Blog.create(blog, options)\n```\n\nIn your story, you can access the context in earlier pipe segments, like this:\n\n```elixir\nstory \"Creating a post\", c\n  |\u003e create_user\n  |\u003e create_blog(c.user)\n  |\u003e create_post(c.blog, post: post_options),\nverify do\n  ...\nend\n```\n\nRead the previous code carefully. Typically, the `user` would not be available from the `c` variable. By making it available with a \nmacro, we make it easy to effortlessly build a simple composition of pipe segments, with the changes of each previous segment \navailable to the next. Notice we're free to specify c.user and c.blog, which otherwise would be out of bounds. We can also take \nadvantage of the same behavior in our setup functions with `assigns`, like this:\n\n```elixir\ndefp blog_with_post(user, title, post) do\n  assign(\n    user: user,\n    blog:create_blog(c.user),\n    post: create_post(c.blog, c.user) )\nend\n```\nThat macro makes composing this kind of data much cleaner.\n\n### defplot (coming soon)\n\nOften, you want to add a single key to a test context. To make things easier for the person reading the test, you would like to make the name of the function in the story block and the key in the context the same. `defplot` makes this easy. You can build a single line of a story, called a `plot`, like this:\n\n```elixir\ndefplot user(name, email) do\n  %User{ name: name || \"Bubba\", email: email || \"gone@fishin.example.com\" }\nend\n```\n\nNote that you can one-line simpler functions as well:\n\n```elixir\ndefplot user(name, email), do:%User{ name: name, email: email }\n```\n\nThat expands to:\n\n```elixir\ndef user(c, name, email) do\n  Map.put c, :user, %User{ name: name, email: email }\nend\n```\n\nSay you have a story block that looks like this:\n\n```elixir\nstory \"Emailing a user\", c\n  |\u003e user,\n  |\u003e application_function_emailing_a_user\nverify do\n  assert c.user.email\nend\n```\n\nNow, it's clear that the `user` plot in the story populates the `:user` key in the context. Your stories are easier to read, and your plot lines are easier to write. Win/win.\n\n## Expected Use\n\nIn True Story, We change the way we think about tests a little bit. Follow these rules and you'll get better benefit out of the framework. \n\n### One experiment, multiple measurements\n\nThe `story` block contains an experiment. The `verify` block conains one or more measurements. You probably noticed that we're not afraid of multiple assertions in our `verify` block. We think that's ok, and it fits our metaphor. We're verifying a story, or measuring the result of an experiment. \n\n### Separation of pure and impure. \n\nAnything that changes the context or the external world *always* goes into `story`. The `verify` is left to pure functions. That means we'll call our `story` blocks exactly once, and that's a huge win. The processing is simpler, and allows the best possible concurrency. \n\n### Reusable Library\n\nOver the course of time, you'll accumulate reusable testing units in your story `library`. The way we're structured encourages this practice, and encourages users to build into composeable blocks. It's easy to roll up smaller library functions into bigger ones using nothing but piping and this is encouraged. \n\n### Experiments raise errors, assertions return data. \n\nThat means we don't have to stop for a failure. Since assertions/measurements are stateless, we don't have to worry about failures corrupting our tests, so these tests can continue to run. We get better cycle times because we can fix multiple tests for a single run while doing green field development or refactoring. \n\n### Everything should compose. In True Story, an integration test is just one test that flows into the next. Story pipe segments are also just compositions on the context. \n\n## Wins\n\nWe didn't release True Story until we'd had six months of experience with it. We can confirm that these techniques work. Here's what we're finding. \n\n- *Tests are first class citizens.* The macros in this library are big wins for the organization of setup functions, and thus tests.\n- *One experiment, multiple measurements.* We find single purpose code gives us prettier tests, and more composable, reusable setups. \n- *Experiments can be stateful; measurements can't.* We can run each setup *once* so we get great performance.\n- *Experiments raise; measurements return fail data.* This means we can return multiple failures per test, shorting cycle times.\n- *Everything composes.* We find that most testing effort is in setup. If setup is simple, the rest of the testing is much easier.\n\nEnjoy. Let us know what you think.\n\nWe're looking into better integration with Phoenix, and better integration with genstage. We're open to ideas and pull requests. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericmj%2Ftrue_story","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericmj%2Ftrue_story","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericmj%2Ftrue_story/lists"}