Making and reusing custom generators with ExCheck

Hi,

Are there any good resources about making custom generators with ExCheck ?

I’m also curious about how they could be written once and reused in many tests.

This is a start: https://github.com/krestenkrab/triq/blob/master/doc/triq_dom.md#domain-3

So assuming I defined the following struct:

defmodule ExTumblr.Post.PhotoMeta do
  @type t :: %__MODULE__{
    width:  number(),
    height: number(),
    url:    String.t
  }

  defstruct ~w(width height url)a
end

I started a custom generator like this:

  def photo_meta_gen do
    domain(
      PhotoMeta,
      fn (self, size) -> SHOULD I GENERATE SOME RANDOM NB AND STRING MYSELF ? end
      fn (self, value) -> SHRINK THE VALUE end
    )
  end

As you can see, I’m not sure whether I must generate random values myself or if I can reuse generators of the triq library…

Thanks for your help

1 Like

So I tried to make my own generator like this:

def photo_meta_gen do
    domain(
      PhotoMeta,
      fn (self, size) ->
        {_, width} = pick(non_neg_integer, size)
        {_, height} = pick(non_neg_integer, size)
        {_, url} = pick(unicode_string, size)
        {self, %{"width" => width, "height" => height, "url" => url}}
      end,
      fn (self, value) ->
        (self, %{"width" => w} = value) when w > 0 -> {self, %{value | "width" => w - 1}}
        (self, %{"height" => h} = value) when h > 0 -> {self, %{value | "height" => h - 1}}
        (self, %{"url" => url} = value) when not(is_nil(url)) -> {self, %{value | "url" => String.slice(url, 0..-2)}}
        (self, value) -> {self, value}
      end
    )
end

Then I changed my property to:

for_all unparsed_photo_meta in photo_meta_gen do
  unparsed_photo_meta ==
    unparsed_photo_meta
    |> PhotoMeta.parse
    |> Poison.encode!
    |> Poison.decode!
end

Then I thought I could try to reuse the shrinkers defined by triq to simplify my custom generator. Example with the url key:

def photo_meta_gen do
    domain(
      PhotoMeta,
      fn (self, size) ->
        {_, width} = pick(non_neg_integer, size)
        {_, height} = pick(non_neg_integer, size)
        {_, url} = pick(unicode_string, size)
        {self, %{"width" => width, "height" => height, "url" => url}}
      end,
      fn (self, value) ->
        (self, %{"width" => w} = value) when w > 0 -> {self, %{value | "width" => w - 1}}
        (self, %{"height" => h} = value) when h > 0 -> {self, %{value | "height" => h - 1}}
        (self, %{"url" => url} = value) -> {self, %{value | "url" => shrink(unicode_string(), url)}}
        (self, value) -> {self, value}
      end
    )
  end

But it doesn’t work, and if I try it myself in iex I get this error:

iex(3)> :triq_dom.shrink(:triq_dom.unicode_string(), "lol")
** (ErlangError) erlang error: {:shrink, {:list, {:@, :unicode_char, #Function<83.73849349/2 in :triq_dom.unicode_char/0>, #Function<84.73849349/2 in :triq_dom.unicode_char/0>, true}}}
    src/triq_dom.erl:163: :triq_dom.error_shrink/2
1 Like

12 posts were split to a new topic: Defining custom generators with PropCheck