D4no0

D4no0

I have currently some functionality that fetches/stores some data in redis using redix. From what I see when it comes to testing, there is nothing specified in the documentation, so I guess the most straightforward way would be to mock it.

Instead of mocking, I was wondering if it would be possible to take the approach the Ecto took, using the real database but being able to test in isolation. It does seem that redis supports concept of transaction, even though not sure how much of it overlaps with how it works in DB engines like Postgres.

What are your thoughts on this?

Showing Posts 1 to 8

mudasobwa

mudasobwa

Creator of Cure

If I had a logic that requires a real redis instance to test (e. g. pubsub subscription, polling from the other place, or like,) I’d definitely supply a PR to redix implementing the testing framework.

I never had such a condition (e. g. I used redis as a pure kv-store,) and since I trust in Andrea’s coding skills, I mock it completely.

D4no0

D4no0 OP

That is true, this is my use-case too. I am not very familiar with redis, this is why I would like the calls to go to a real instance, just in case some of my assumptions when it comes to mocking are wrong, I would rather spend my time on a better testing setup than debugging that on deployed thing.

mudasobwa

mudasobwa

Creator of Cure

What an original approach! :slight_smile:

Well, then you might want to open the issue to:

  • declare Redix.Behaviour
  • make Redix implement it
  • create Redix.Test implementing this same behaviour and mimicking Redix by optionally wrapping each and every call to transaction_pipeline/3 and telemetrying it

Since redis doesn’t have a rollback mechanism at all, you’ll need to WATCH the data and/or implement the rollback manually. Here is a good blog about why You Don’t Need Transaction Rollbacks in Redis.

You anyway would need to implement all the above, hence my proposal to do a PR.

D4no0

D4no0 OP

I won’t have time to do this now, as I will need some time to understand how test setups work and how to do the rollbacks correctly (so it would work with async tests too), but I would be most certainly interested in adding the feature as soon as I get a little bit more time as there is a lot of redis usage at the current projects I work on.

I wonder if it makes sense to make the implementation more abstract, as I will be most probably be interested in doing this with MongoDB tests too and I bet there is no way to do that ATM.

mudasobwa

mudasobwa

Creator of Cure

Should not we summon @whatyouhide here?

I am thinking about something that would make it possible to write tests like

test "redix_interop" do
  assert_redix_set "foo", 42, fn -> send(MyProcess, {:foo, 42}) end
  assert_redix_change, "bar", 3, fn -> MyModule.update_bars(& &1 + 3) end
  assert_redix ...
end
rkallos

rkallos

The approach I took to testing an application that used Redis was to start multiple Redis instances.

I used an Agent to pass out unused ports to tests, and would start a Redis server listening on that unused port using System.cmd/3, then pass the port in the ExUnit context, which would find its way into the app configuration. That way, each test that required Redis could have its own unique instance, which meant tests could be run concurrently.

While this isn’t very efficient, it works well enough during local development and in CI that it isn’t enough of an issue to replace at the moment.

Here’s the module I wrote for starting Redis instances:

defmodule Banker.Helper.RedisPortAgent do
  @moduledoc false
  use Agent

  @spec start_link() :: Agent.on_start()
  def start_link do
    opts = [name: __MODULE__]
    Agent.start_link(fn -> 20_000 end, opts)
  end

  @spec unique_redis() :: {:ok, map()}
  def unique_redis do
    port = Agent.get_and_update(__MODULE__, fn port -> {port, port + 1} end)

    wrapper = Path.join([:code.priv_dir(:banker), "port_wrapper.sh"])
    redis_server = System.find_executable("redis-server") || raise "redis-server not found"

    redis =
      Port.open(
        {:spawn_executable, wrapper},
        args: [
          redis_server,
          "--port",
          to_string(port),
          "--save",
          ~S("")
        ]
      )

    {:ok, %{port: port, redis: redis}}
  end
end

and port_wrapper.sh:

#!/usr/bin/env bash

# Start the program in the background
exec "$@" &
pid1=$!

# Silence warnings from here on
exec >/dev/null 2>&1

# Read from stdin in the background and
# kill running program when stdin closes
exec 0<&0 $(
  while read; do :; done
  kill -KILL $pid1
) &
pid2=$!

# Clean up
wait $pid1
ret=$?
kill -KILL $pid2
exit $ret
sorentwo

sorentwo

Oban Core Team

You don’t necessarily need multiple Redis instances. Every instance has 16 “databases” by default, addressable from 0 through 15. The data in each of those databases is completely isolated, and you’re actually able to selectively flush a single db.

You can use separate database instances for each test case (anything that is testing for the presence of specific values) and then call flushdb afterwards to clear only that database.

whatyouhide

whatyouhide

Elixir Core Team

I don’t think Redix itself should change to accommodate testing here. Redix is concerned with talking to the Redis database, and it gives you enough tools that you can build testing utilities on top of it. The databases examples that @sorentwo mentioned is already a really good way to go with at least 16 parallel tests using Redis. Multiple instances also work—Redis is really cheap to run.

Redis itself doesn’t support complex transactions the way relational DBs (Postgres and co) that Ecto supports do, so implementing a sandbox here would not really be possible.

In case mocks are your choice, I don’t think Redix should declare and/or implement behaviours for that either. Redix’s API is pretty slim and chances are you’re not using all of it, so declaring the interface you need to talk to Redis for your use case and then declaring mocks for that seems like a good approach to me.

— All posts loaded —

Where Next? Top

Trending in Questions Top

itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
yarrichar
I’m very new to Elixir, so sorry if this is a silly question: I am wondering what happens to the mailbox of a process when the machine i...
New
pragdave
How do I tell Hologram not to escape HTML when substituting into templates? {Textile.render(@product.long_description)} escapes the HTM...
New
Fl4m3Ph03n1x
Background When trying to execute mix release on a Windows 11 machine for a Phoenix project I get the following error: * assembling mark...
New
rlipscombe
I’d like to create a file with 600 permissions (rw-------), but there’s a race condition if I create it and then change the permissions. ...
New
saleyn
Is there a way to import record definitions in the iex shell for pretty printing (similar to the rr command in the erl shell)? This woul...
New
rlipscombe
When I use Mix.install with a git dependency, using git@github.com:... how do I change the SSH identity used? I’ve got two Github accoun...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews