Jyoti Gautam
Jyoti Gautam

Reputation: 41

Testing an elixir function

I have a Product schema which has a UPI(unique product identifier) eg. A985748BNG6784C. This is an autogenerated unique product identifier.

I have a function upi_generate() which calls another external function gen_nano_id() to generate this random unique id.

If by chance, the id generated by gen_nano_id() has already been generated, the function upi_generate() calls itself recursively till the time gen_nano_id() generates a unique id. Thus generating a unique UPI.

gen_nano_id() can sometimes return duplicate ids and for that purpose I have written the below code with recursive call.

  def gen_nano_id() do  //external function
      Nanoid.generate(10, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
  end

  # TODO: write test case for this
  def upi_generate() do // required function
      upi = "A" <> gen_nano_id() <> "C"

      case get_product_with_upi(upi) do
         nil ->
            upi 
         _ ->
            upi_generate()
      end
  end

  // Check if product with UPI already exists
  defp get_product_with_upi(upi) do
      from(p in "snitch_products", select: p.upi, where: p.upi == ^upi) 
      |> Repo.one()
  end

Now, I have to test the id regeneration logic for duplicate ids.

My testing approach involves following logic. Create a two products with duplicate UPI and try to reach the _ part of the case comparison.

For this purpose, I have mocked(I don't control the behaviour of this function) the gen_nano_id().

Now, the problem that I am facing is mocking results in creation of always the same ids no matter what and I go in an infinite loop.

I am not able to figure out a way to reach the exit condition(nil) part of case comparison with this mocking approach of gen_nano_id.

Upvotes: 1

Views: 109

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

There is great writing by José Valim Mocks and explicit contracts. It says

always consider “mock” to be a noun, never a verb


That means you should not mock the generation function. You’d better create a generator and mock it.

Somewhat like that:

defmodule Generator do
  @callback gen_id :: integer()
end

defmodule NanoGenerator do
  @moduledoc "Used in dev/prod"
  @behaviour Generator

  @impl true
  def gen_id() do
    get_nano_id() # external function or whatever
  end
end

defmodule TestGenerator do
  @moduledoc "Used in test"
  @behaviour Generator

  use Agent # to store state
  @ids ~w|foo foo bar|

  @impl true
  def gen_id() do
    id = # get the counter from Agent, and increase it
    @ids[id]
  end 
end

Now you are all set to return whatever you want from the generator.

Upvotes: 3

Related Questions