đź“”
Idiosyncratic Elixir
  • Introduction
  • Testing in General
    • Flow style tests
  • Phoenix View Models
    • Single form view models
    • Testing view models
  • Declarative testing of structured (form) input
    • Preface
    • Features, described gradually
      • Describing changeset validations
      • Running tests
      • Example prototypes
      • Shorthand for built-in transformations (`cast`)
      • Shorthand for custom transformations (on_success)
Powered by GitBook
On this page

Was this helpful?

  1. Declarative testing of structured (form) input

Preface

An argument for the idea

PreviousTesting view modelsNextFeatures, described gradually

Last updated 4 years ago

Was this helpful?

Many people have noticed that a lot of programming–especially functional programming–involves taking structured data, transforming it, producing a result, and possibly persisting that result (to, for example, a database).

Because of that, we work a lot with libraries (like or ) that help in writing such transformation code. But we generally take less advantage of that when testing or test-driving such code. That is: the product code we write is clichéd, full of recognizable idioms, patterns, and names. We build on that. But test code doesn't take similar advantage of the structure of the problem. In these pages, I'll describe test code that does.

Such test code can be simpler–more declarative–than the code it's testing. The reason is that conventional[1] test code is essentially a list of specific examples, together with results checking that's specific to the examples. Product code must handle all possible inputs, which is more work. In particular, product code is "branchy", whereas that's vanishingly rare in test code.

[1] I'm going to leave aside generated or property-based tests like . Because they don't have a fixed set of inputs, their checks have to be more general-purpose, though still not as complicated as product code.

A Teaser

A whole lot of assertions are generated from this:

defmodule Examples.Schemas.Named do
  alias App.Schemas.Named, as: Named
  use TransformerTestSupport.Variants.EctoClassic

  def create_test_data do 
    start(
      module_under_test: Named,
      format: :phoenix
    )

    |> field_transformations(
      as_cast: [:name, :date_string, :lock_uuid],
      date: on_success(
        Date.from_iso8601!(:date_string)),
      days_since_2000: on_success(
        Date.diff(:date, ~D[2000-01-01])
    ))

    |> category(                           :success,
      ok: [
        params(      name: "Bossie", date_string:   "2001-01-01"),
      ]
    )

    |> category(                           :validation_failure,
      format: [
        params_like(:ok, except: [date_string: "2001-01-0"]),
        changeset(
          no_changes: [:date, :days_since_2000],
          error: [date_string: "is not a valid date"]
        ),
      ],
    
      too_early: [
        params_like(:ok, except: [date_string: "1999-12-30"]),
        changeset(
          no_changes: [:days_since_2000],
          error: [date_string: "must be this century"]
        ),
      ]
    )
  end
end
Phoenix
Ecto
PropCheck