Code Monkey home page Code Monkey logo

assertions's Introduction

Assertions Hex Version Build Status

Installation

Add assertions to your list of dependencies in mix.exs:

def deps do
  [{:assertions, "~> 0.10", only: :test}]
end

Usage

Importing

If you only want some assertions in a given module, then import just the functions that you want to use. Otherwise you can simply call import Assertions and all assertions will be directly available to your test code.

def UsersTest do
  use ExUnit.Case, async: true

  require Assertions
  import Assertions, only: [assert_lists_equal: 2]

  # ...
end

Because these assertions are all macros, Assertions must be required first if you want to call a function like Assertions.assert_map_in_list/3.

Importing assertions in an existing test case (like MyApp.DataCase in a Phoenix application) is typically recommended.

Here is an example of how you'd add assertions to your MyApp.DataCase:

defmodule MayApp.DataCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      import Ecto
      import Ecto.Changeset
      import Ecto.Query

      import MyApp.DataCase

      # Add the following line
      import Assertions
    end
  end
end

Assertions.Case

If you want to have all assertions available to you by default, you can use the provided Assertions.Case macro. This is a very small wrapper around ExUnit.Case, and imports all assertions for your use.

def MyApp.UserTest do
  use Assertions.Case, async: true

  # ...
end

But importing assertions in an existing test case (like MyApp.DataCase in a Phoenix application) is typically recommended.

Why use assertions?

There are three things this library offers:

  1. Concise, expressive assertions for common types of tests
  2. Flexibility through composition
  3. Exceptional error messages

Let's look at examples of all of these points. Let's say you have the following test:

defmodule UsersTest do
  use ExUnit.Case, async: true

  describe "update_all/2" do
    test "updates the given users in the database and returns those updated users" do
      alice = Factory.insert(:user, name: "Alice")
      bob = Factory.insert(:user, name: "Bob")

      updated_names = 
        [{alice, %{name: "Alice A."}, {bob, %{name: "Bob B."}}}]
        |> Users.update_all()
        |> Enum.map(& &1.name)

      all_user_names =
        User
        |> Repo.all()
        |> Enum.map(& &1.name)

      Enum.each(["Alice A.", "Bob B."], fn name ->
        assert name in updated_names
        assert name in all_user_names
      end
    end
  end
end

Testing elements in lists is a very common thing to do, but it's also very tricky! If you want to test those lists, you can't assert that they're equal because order matters with lists. Also, with those structs, you can't compare them directly because maybe there are associations that might be loaded in one struct but not loaded in the other. The above test is the best you can do to accurately test those changes with the standard testing tools.

Expressive assertions

But, with assertions, you can write that test like this:

defmodule UsersTest do
  use ExUnit.Case, async: true
  import Assertions, only: [assert_lists_equal: 2]

  describe "update_all/2" do
    test "updates the given users in the database and returns those updated users" do
      alice = Factory.insert(:user, name: "Alice")
      bob = Factory.insert(:user, name: "Bob")

      result = Users.update_all([{alice, %{name: "Alice A."}, {bob, %{name: "Bob B."}}}

      result
        |> Enum.map(& &1.name)
        |> assert_lists_equal(["Alice A.", "Bob B."])

      assert_lists_equal(result, Users.list_all(), &assert_structs_equal(&1, &2, [:name]))
    end
  end
end

assert_lists_equal asserts that the two lists are equal without taking order into account, which is most often the assertion that we want to make when comparing lists.

Flexibility through composition

But assert_lists_equal also solves the other problem we had when we wanted to compare lists of structs. That second assertion:

assert_lists_equal(result, Users.list_all(), &assert_structs_equal(&1, &2, [:name]))

is comparing that the two lists are equal, but we give it a custom comparison function. If we were to just use assert_lists_equal(results, Users.list_all()), then all values for all keys in those structs must be equal for them to be considered equal, and this can be very error prone, especially when dealing with structs used as Ecto resources that can have associations that are either loaded or not loaded.

This ability to compose behavior of assertions lets you easily customize your assertions for each of your tests.

Exceptional error messages

assertions always tries to give you the most helpful error messages possible for any test failures to make it easy to see what went wrong and how to fix it. Lets look at a different test:

test "a map in a list matches the value of this other map" do
  map = %{key: :value, stores: :are, really: :helpful}

  list = [
    %{big: :map, with: :lots, of: :keys},
    %{another: :big, store: :with, key: :values}
  ]

  assert Enum.any?(list, fn map_in_list ->
            Map.get(map_in_list, :key) == map.key
          end)
end

The output you get from that failure looks like this:

     Expected truthy, got false
     code: assert Enum.any?(list, fn map_in_list -> Map.get(map_in_list, :key) == map.key() end)
     arguments:

         # 1
         [
           %{big: :map, of: :keys, with: :lots},
           %{another: :big, key: :values, store: :with}
         ]

         # 2
         #Function<21.25555998/1 in Assertions.FailureExamples."test example"/1>

     stacktrace:
       test/failure_examples.exs:207: (test)

That's not really helpful. What we wanted to know was essentially "is a map with the same values for a certain key in this list?", and we got no help in finding what went wrong. With assertions we can write that same assertion like this:

test "a map in a list matches the value of this other map" do
  map = %{key: :value, stores: :are, really: :helpful}

  list = [
    %{big: :map, with: :lots, of: :keys},
    %{another: :big, store: :with, key: :values}
  ]

  assert_map_in_list(map, list, [:key])
end

And then the output looks like this:

     Map matching the values for keys `:key` not found
     code:  assert_map_in_list(map, list, [:key])
     arguments:

         # 1
         %{key: :value, really: :helpful, stores: :are}

         # 2
         [
           %{big: :map, of: :keys, with: :lots},
           %{another: :big, key: :values, store: :with}
         ]

     left:  %{key: :value}
     right: [%{}, %{key: :values}]
     stacktrace:
       test/failure_examples.exs:206: (test)

The error message there in the left and right keys shrinks down the output to only show us the relevant information. I can see here that one map didn't contain the key we wanted, and the value was different in the other.

assertions's People

Contributors

devonestes avatar jmnsf avatar jueberschlag avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.