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!
CodePudding user response:
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:
- Specify the sandbox adapter and a dedicated test database in your app config, e.g. 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...
- 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 [] = R.get_people()
end
end
end
A couple more pointers:
Your
get_people/0
function, as written, will not return theresult
. 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 theresult
you are expecting.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 executemix ecto.migrate
prior to tests, or you can just remember to runMIX_ENV=test mix ecto.drop
etc. against the test database.You'll often find it helpful to automatically build data "fixtures", i.e. 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", e.g. where you can override the Repo
module as a function parameter or as something pulled from config, e.g. get_people(repo \\ Repo)
CodePudding user response:
I'd suggest running PostgreSQL in a container, then using Ecto Sandbox. https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html
CodePudding user response:
ecto 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.