co0lsky

co0lsky

Request for code review - property based test

Greetings! I am relatively new to property-based testing. I am practicing writing property tests and hoping for a code review if any practitioner has any comments or suggestions on the test.

defmodule Academy.UserPropTest do
  alias Academy.UserShim

  use Academy.TestCase, async: true
  use PropCheck
  use PropCheck.StateM

  require ExUnitProperties

  # Generators
  def first_name() do
    StreamData.string(:alphanumeric, length: 10..20)
  end

  def email() do
    StreamData.string(:alphanumeric, length: 10)
  end

  def email(s) do
    attrs = Map.get(s, :existing)
    StreamData.constant(attrs.email)
  end

  def uid() do
    StreamData.string(:alphanumeric, length: 10)
  end

  def uid(s) do
    attrs = Map.get(s, :existing)
    StreamData.constant(attrs.uid)
  end

  def role() do
    StreamData.member_of(["admin", "editor", "tester", "support", "user"])
  end

  def profile() do
    StreamData.nonempty(StreamData.map_of(StreamData.constant("lang"), StreamData.string(:ascii)))
  end

  def attrs() do
    Enum.take(
      StreamData.fixed_map(%{
        first_name: first_name(),
        email: email(),
        uid: uid(),
        role: role(),
        profile: profile()
      }),
      1
    )
  end

  def attrs(s) do
    Enum.take(
      StreamData.fixed_map(%{
        first_name: first_name(),
        email: email(s),
        uid: uid(),
        role: role(),
        profile: profile()
      }),
      1
    )
  end

  # Helpers
  def like_email(map, email) do
    Enum.any?(
      Map.values(map),
      fn attrs -> attrs.email == email end
    )
  end

  property "user stateful operations", [:verbose] do
    forall cmds <- commands(__MODULE__) do
      {history, state, result} = run_commands(__MODULE__, cmds)

      (result == :ok)
      |> aggregate(command_names(cmds))
      |> when_fail(
        IO.puts("""
        History: #{inspect(history)}
        State: #{inspect(state)}
        Result: #{inspect(result)}
        """)
      )
    end
  end

  # initial model value at system start. Should be deterministic.
  def initial_state(), do: %{}

  def command(state) do
    always_possible = [
      {:call, UserShim, :create, attrs()}
    ]

    relies_on_state =
      case Map.equal?(state, %{}) do
        # no values yet
        true ->
          []

        # values from which to work
        false ->
          s = state

          [
            {:call, UserShim, :create_existing, attrs(s)}
          ]
      end

    oneof(always_possible ++ relies_on_state)
  end

  # Picks whether a command should be valid under the current state.
  def precondition(s, {:call, _, :create, [attrs | _]}) do
    not like_email(s, attrs.email)
  end

  # - all calls with known emails
  def precondition(s, {:call, _mod, _fun, [attrs | _]}) do
    like_email(s, attrs.email)
  end

  # Given the state *prior* to the call {:call, mod, fun, args},
  # determine whether the result (coming from the actual system)
  # makes sense.
  def postcondition(_state, {_, _mod, :create, _args}, {:ok, _}) do
    true
  end

  def postcondition(_state, {_, _mod, :create_existing, _args}, {:error, _}) do
    true
  end

  # Assuming the postcondition for a call was true, update the model
  # accordingly for the test to proceed
  def next_state(
        state,
        _,
        {:call, _, :create, [attrs]}
      ) do
    Map.put(state, :existing, attrs)
  end

  def next_state(state, _res, {:call, _mod, _fun, _args}) do
    new_state = state
    new_state
  end
end
defmodule Academy.UserShim do
  def create(attrs) do
    Academy.User.create(attrs)
  end

  def create_existing(attrs) do
    Academy.User.create(attrs)
  end
end

First Post!

dimitarvp

dimitarvp

Nothing really wrong pokes me in the eye. Do you want to add to this code but are not able to? That second module is a bit strange though, what’s its purpose?

Last Post!

co0lsky

co0lsky

This is my first time writing a property test. I hope to know if the test has some bad practices.

The second module is a Shim. In my case, I would like to test a different scenario, such as create a user who already exists in the system. By using the shim, I have :create and :create_existing to indicate different scenarios. So, if calling :create_existing, an error is expecting instead of success.

Where Next?

Popular in Questions Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement