Jeremy Belolo
Jeremy Belolo

Reputation: 4539

Elixir - testing a full script

I'm writing a test to check a function (called automatically by GenServer when a new file enters a folder) that calls other functions in the same module with pipes in order to read a file, process its content to insert it if needed and returns a list (:errors and :ok maps).

results looks like :

[
          error: "Data not found",
          ok: %MyModule{
            field1: field1data,
            field2: field2data
          },
          ok: %MyModule{
            field1: field1data,
            field2: field2data
          },
          error: "Data not found"

the code :

  def processFile(file) do
    insertResultsMap =
      File.read!(file)
      |> getLines()
      |> extractMainData()
      |> Enum.map(fn(x) -> insertLines(x) end)
      |> Enum.group_by(fn x -> elem(x, 0) end)

    handleErrors(Map.get(insertResultsMap, :error))
    updateAnotherTableWithLines(Map.get(insertResultsMap, :ok))
  end

  defp getLines(docContent) do
    String.split(docContent, "\n")
  end

  defp extractMainData(docLines) do
    Enum.map(fn(x) -> String.split(x, ",") end)
  end

  defp insertLines([field1, field2, field3, field4]) do
    Attrs =  %{
      field1: String.trim(field1),
      field2: String.trim(field2),
      field3: String.trim(field3),
      field4: String.trim(field4)
    }

    mymodule.create_stuff(Attrs)
  end

  defp handleErrors(errors) do
    {:ok, file} = File.open(@errorsFile, [:append])
    saveErrors(file, errors)
    File.close(file)
  end

  defp saveErrors(_, []), do: :ok
  defp saveErrors(file, [{:error, changeset}|rest]) do
    changes = for {key, value} <- changeset.changes do
      "#{key} #{value}"
    end
    errors = for {key, {message, _}} <- changeset.errors do
      "#{key} #{message}"
    end

    errorData = "data: #{Enum.join(changes, ", ")} \nErrors: #{Enum.join(errors, ", ")}\n\n"

    IO.binwrite(file, errorData)
    saveErrors(file, rest)
  end

  defp updateAnotherTableWithLines(insertedLines) do
    Enum.map(insertedLines, fn {:ok, x} -> updateOtherTable(x) end)
  end

  defp updateOtherTable(dataForUpdate) do
    "CLOSE" -> otherModule.doStuff(dataForUpdate.field1, dataForUpdate.field2)
  end

I have several questions, and some will be pretty basic since I'm still learning :

Upvotes: 0

Views: 109

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

What do you think of the code? Any advices? (take into account I voluntarily obfuscated names).

Opinion based.

If I want to test this, is it the right way to test only processFile function?

Yes.

Or should I make public more of them and test them individually?

No, this is an implementation detail and testing it is an anti-pattern.

When I test the processFile function, I check that I'm receiving a list. Any way to make sure this list has only elements I'm waiting for, thus error: "String" or ok: %{}"?

You receive a Keyword. To check the explicit value, one might use:

foo = processFile(file)
assert not is_nil(foo[:ok])

OTOH, I’d better return a map from there and pattern match it:

assert %{ok: _} = processFile(file)

To assert that the result does not have anything save for :oks and :errors, one might use list subtraction:

assert Enum.uniq(Keyword.keys(result)) -- [:ok, :error] == []

Upvotes: 1

Related Questions