简体   繁体   中英

Test coverage in repo class using Ecto in Elixir

I have a very simple app in Elixir that uses Ecto, to run a SELECT query in a table on Postgres. The table name is "person" and has 2 columns: "name" and "age".

This is the app:

/ecto_app
 |-application.ex
 |-repo.ex

application.ex:

defmodule EctoApp.Application do

  use Application

  require Logger

  @impl true
  def start(_type, _args) do
    children = [
      EctoApp.Repo
    ]

    opts = [strategy: :one_for_one, name: EctoApp.Supervisor]
    Logger.debug("Starting Ecto App")
    Supervisor.start_link(children, opts)
  end
end

repo.ex:

defmodule EctoApp.Repo do
  use Ecto.Repo,
    otp_app: :ecto_app,
    adapter: Ecto.Adapters.Postgres

  import Ecto.Query

  require Logger


  def get_people() do
    query = from p in "person",
            select: [p.name, p.age]
    result= EctoApp.Repo.all(query)
    Logger.debug(result)
  end

end

and I'm using this 2 libraries:

[
  {:ecto_sql, "~> 3.0"},
  {:postgrex, ">= 0.0.0"}
]

The app works fine, but the problem is that I want to make unit tests for the repo, but I can´t figure out how to Mock the Postgres connection, since I don´t want to start a Postgres instance to run the tests.

Did anyone knows how to Mock or which library I can use to "simulate" the connection to Postgres, so I can have test coverage on the repo class?

Thanks!

You don't need to mock the PostGres connection in the same way that you might mock other functionality. This is because PostGres can wrap operations inside an undoable transaction. The takeaway is that your tests use a real connection and make real requests against a real database, but the database is one dedicated to testing and issue queries via a special adapter. This is by far the most common way to achieve test coverage for a database-backed app; attempting to mock the database connection makes for brittle tests rife with false positives, so it is not commonly done, especially when it is so simple to issue all your requests against a real database.

Here are the important bits to facilitate this in your code:

  1. Specify the sandbox adapter and a dedicated test database in your app config, eg via config files:
# config/dev.exs
config :my_app, MyApp.Repo,
  pool: DBConnection.ConnectionPool,
  database: "something_dev",
  # ... etc...

# config/test.exs
config :my_app, MyApp.Repo,
  pool: Ecto.Adapters.SQL.Sandbox,
  database: "something_test",
  # ... etc...
  1. Your tests must checkout the Repo using the adapter prior to running queries. This is what wraps the actions in a transaction so you can safely re-run tests without excessive cleanup. A good place for this is inside the setup callback , which runs prior to every test in the module:
defmodule MyApp.SomeTest do
  use ExUnit.Case # you may require async: false

  alias Ecto.Adapters.SQL.Sandbox
  alias MyApp.Repo

  setup do
    Sandbox.checkout(Repo)
  end

  describe "get_people/0" do
    test "select" do
      # insert fake records here
      assert [] = Repo.get_people()
    end
  end
end

A couple more pointers:

  1. Your get_people/0 function, as written, will not return the result . Remember that Elixir relies on implicit returns, so it returns the result of the last operation. In your case, the thing returned will be the result of the debug statement, which is just an :ok and NOT the result you are expecting.

  2. Don't forget that you may need to run migrations against your test database -- this isn't something that happens automagically. You can make an alias in your mix.exs to execute mix ecto.migrate prior to tests, or you can just remember to run MIX_ENV=test mix ecto.drop etc. against the test database.

  3. You'll often find it helpful to automatically build data "fixtures", ie fake records in the proper shape. The ex_machina package is a handy way to do this, but no matter if you use a package or roll your own fixtures, your tests will need to prepare the database with whatever test records you expect as part of the "arrange" step in the standard "arrange-act-assert" methodology of testing.

If you circumstance truly requires that you mock the database connection , then I would recommend a simple pattern that relies on a kind of "dependency injection", eg where you can override the Repo module as a function parameter or as something pulled from config, eg

def get_people(repo \\ Repo)
  query = from p in "person",
    select: [p.name, p.age]
  repo.all(query)
end

I'd suggest running PostgreSQL in a container, then using Ecto Sandbox. https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html

comes with its own testing suite and it has dedicated documentation on How to Test with Ecto .

Ecto.Adapters.SQL.Sandbox is to be used to test but there is no way to avoid starting the actual DB instance, mostly because it makes a very little sense to use mocked connections for testing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM