alicebob/miniredis: a pure Go Redis server for Go unit tests
Pure Go Redis server for Go unittests
At a glance
- What is it?
- Miniredis implements part of the Redis command set inside your test process, with a real TCP interface and no external binary. It is a good fit for code that talks to Redis through a client library, and a poor fit for anything that depends on the commands it does not implement.
- Who is it for?
- Adopt miniredis if your Go tests exercise Redis through a client library and stay inside the implemented command set; skip it if you need full Redis semantics, server-side scripting beyond the EVAL family, or a non-Go language. Before committing, check the command list in the README against every command your code issues, and decide whether TTLs should be advanced with FastForward or with ClockTTL.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem miniredis solves for Go tests that touch Redis
Code that reads and writes Redis is awkward to unit test. Mocking the client library means writing fake command handlers that drift from real Redis behaviour, and running a real redis-server means the test suite depends on an external binary being installed and started. Miniredis takes a third route: it implements parts of the Redis server in Go, in memory, inside the test process, and exposes a real TCP interface.
The README frames it as the Redis version of net/http/httptest. That comparison is the useful one. Your production code keeps using its normal Redis client and its normal connection string; only the address changes. Because the server lives in the same process, the test can also read and write keys directly through Go methods such as Set, HSet and Get, without going through the client stack at all.
The audience is narrow and specific: Go developers writing unit tests for packages that use Redis. If your tests are already integration tests against a real server, miniredis is a downgrade. If your tests are full of hand-written mocks for Redis calls, it removes that layer.
How the in-process server is put together
The repository layout shows the shape of the thing. Each command family has a file: cmd_string.go, cmd_hash.go, cmd_list.go, cmd_set.go, cmd_sorted_set.go, cmd_stream.go, cmd_geo.go, cmd_hll.go, cmd_pubsub.go, cmd_transactions.go, cmd_scripting.go, cmd_cluster.go, cmd_connection.go, cmd_server.go, cmd_generic.go, cmd_info.go and cmd_object.go. Each has a matching _test.go file. The keyspace and expiration logic sit in db.go and keys.go, and the direct Go accessors are in direct.go.
The dependency list is short. go.mod declares one requirement, github.com/yuin/gopher-lua v1.1.1, which is what backs the scripting commands. There is no Redis source tree in the module and no cgo. The integration directory is a separate harness the Makefile drives when you want to compare against a real server.
Time is the part worth understanding before you write tests. The README states that TTLs do not decrease automatically. TTL() returns a time.Duration and gives 0 when no TTL is set. m.FastForward(d) decrements all TTLs and removes any that reach zero. EXPIREAT and PEXPIREAT values are converted to a duration, using either the time set by m.SetTime(t) or time.Now() if SetTime was never called. SetTime also sets what TIME returns, and FastForward does not move it.
m.ClockTTL(true) is the alternative: TTLs then decrease as the clock advances, and expired keys are removed lazily whenever a database is accessed through a command or a Miniredis accessor. The README is explicit about the trade-off. Against the wall clock, expiry depends on how fast the test runs, which is why it is not the default. Combined with SetTime, expiry stays coherent with TIME and (P)EXPIREAT.
Randomness is handled the same way. Miniredis uses math/rand's global RNG unless you call m.Seed(...), in which case it uses its own RNG from that seed. RANDOMKEY, SPOP and SRANDMEMBER are the commands the README lists as using randomness.
Installing miniredis and writing a first test
There is no binary to install and no server to start. You add the module to a Go project and import v2, which the README calls out explicitly.
go get github.com/alicebob/miniredis/v2import "github.com/alicebob/miniredis/v2"The README's example uses miniredis.RunT(t), which starts a server and registers cleanup with the test. You can preload keys your code expects, then run the code against s.Addr().
func TestSomething(t *testing.T) {
s := miniredis.RunT(t)
s.Set("foo", "bar")
s.HSet("some", "other", "key")
c, err := redis.Dial("tcp", s.Addr())
_, err = c.Do("SET", "foo", "bar")
if got, err := s.Get("foo"); err != nil || got != "bar" {
t.Error("'foo' has the wrong value")
}
s.CheckGet(t, "foo", "bar")
}The example in the README dials with the redigo client from github.com/gomodule/redigo/redis. Any client that accepts a TCP address should work the same way, because the interface is a real listener. CheckGet is the shorthand assertion; Get returns the value and an error. After the test finishes, RunT's cleanup closes the server.
If you are running inside a testing/synctest bubble on Go 1.25 or later, the README says real TCP connections cannot be used. Use m.Dial() instead, which serves the connection over an in-memory pipe, and construct the server with NewMiniRedis() so no listener is ever created. With ClockTTL(true) inside a bubble, time.Now() is the bubble's fake clock, so a plain time.Sleep expires keys and FastForward is unnecessary.
synctest.Test(t, func(t *testing.T) {
m := miniredis.NewMiniRedis()
defer m.Close()
m.ClockTTL(true)
client := redis.NewClient(&redis.Options{
Dialer: func(ctx context.Context, _, _ string) (net.Conn, error) {
return m.Dial()
},
})
ctx := context.Background()
client.Set(ctx, "session", "v", time.Hour)
time.Sleep(time.Hour + time.Second)
})Where the implemented command set runs out
The README lists the implemented commands, and the list is long but explicitly partial. Several entries carry qualifiers. DUMP and RESTORE only handle string keys. DELEX is marked partly. COMMAND is partly. INFO is partly and returns only a "clients" section with one field, connected_clients. XINFO STREAM and XINFO CONSUMERS are partly implemented. WAIT is a no-op. GEOHASH is struck through in the list.
Those qualifiers matter more than the missing commands. A test that calls INFO expecting memory or replication fields will get a much smaller reply than production Redis gives, and the failure may look like a parsing bug in your own code rather than a gap in the test server. The same goes for WAIT: a test that asserts on replication acknowledgement is testing nothing.
The command list covers Connection, Key, Transactions, Server, String, Hash, List, Pub/Sub, Set, Sorted Set, Stream, Scripting, GEO, Cluster and HyperLogLog families, so the coverage is broad. But breadth is not the same as fidelity. Miniredis is the wrong tool when you are testing behaviour that lives in the parts of Redis it does not model: persistence, replication, eviction policy, memory accounting, or the exact reply shapes of the partly implemented commands.
A second constraint is operational rather than functional. Because the server runs in the test process, every test that starts one allocates its own keyspace. That is a feature for isolation and a cost if you were hoping to share state across tests. The README does not document a way to snapshot and restore a keyspace between tests.
Miniredis compared with running a real Redis in tests
The obvious alternative is a real redis-server started by the test harness, which is what the repository's own integration directory does. The Makefile has an int target that runs integration tests without downloading a server, and a ci target that runs the unit tests, then builds a real Redis from source under integration/redis_src and runs the integration suite against it, then runs the unit tests again with the race detector.
That split tells you what the maintainers think each layer is for. The real server is the reference implementation; miniredis is the fast path. A real server gives you every command, real persistence semantics and real reply shapes, at the cost of an external binary, a process to manage, and slower tests. Miniredis gives you a Go module with one dependency and no external process, at the cost of the partial command set described above.
If you are not writing Go, miniredis is not an option at all. It is a Go package, not a standalone server binary you can point another language at. The related searches include miniredis rust and tokio miniredis, which suggests people look for equivalents in other ecosystems; this repository does not provide one. For non-Go projects, a real Redis instance or a container is the practical route.
Maintenance, licensing and the cost of upgrading
The repository is not archived, and the last push was on 2026-09-19. Recent releases are v2.39.0 on 2026-09-02 with GEOSEARCH, HPERSIST and PZPOP*, v2.38.0 on 2026-05-12 with DELEX and fixes, and v2.37.0 on 2026-02-25 with HEXPIRE. The cadence is roughly a release every few months, driven by added commands rather than by a fixed schedule.
The upgrade cost is low in structure and non-zero in behaviour. The module path is versioned as /v2, so major-version churn is handled by the import path. The single runtime dependency is gopher-lua, pinned at v1.1.1 in go.mod, and the module targets Go 1.17. Because miniredis is a test dependency, an upgrade can change what your tests observe: a newly implemented command may return a different reply than the previous version's fallback, and a fix can turn a previously passing assertion into a failing one. That is the point of upgrading, but it means the test suite is the thing you are changing.
Licensing is MIT, which is permissive and imposes few conditions on how you redistribute or modify the code. That is a statement about the licence text, not legal advice; if your organisation has a policy on dependency licences, run it through that process.
Editorial conclusion
Adopt miniredis if your Go tests exercise Redis through a client library and stay inside the implemented command set; skip it if you need full Redis semantics, server-side scripting beyond the EVAL family, or a non-Go language. Before committing, check the command list in the README against every command your code issues, and decide whether TTLs should be advanced with FastForward or with ClockTTL. The v2 import path is the one the README tells you to use.
Frequently asked questions
What is miniredis?
It is a pure Go Redis test server used in Go unit tests. It implements parts of the Redis server in memory with a real TCP interface, and the README describes it as the Redis version of net/http/httptest.
How do I install miniredis in a Go project?
Run go get github.com/alicebob/miniredis/v2 and import github.com/alicebob/miniredis/v2. The README says to be sure to import v2, and there is no external binary to install.
Does miniredis need a running Redis server or a Docker container?
No. The README states there are no dependencies on external binaries, so it can be integrated into automated build processes. The server runs inside the test process and exposes a TCP address through s.Addr().
How do TTLs and key expiration work in miniredis?
TTLs do not decrease automatically. You call m.FastForward(d) to decrement them, or m.ClockTTL(true) to make them decrease as the clock advances, in which case expired keys are removed lazily on access.
Does miniredis implement every Redis command?
No. The README lists the implemented commands, and several are marked partly, including DUMP, RESTORE, DELEX, COMMAND, INFO and parts of XINFO. WAIT is a no-op and GEOHASH is struck through.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/alicebob-miniredis)