SoFunction
Updated on 2025-03-05

Go unit test Mock test on database CRUD

Preface

Recently, in practice, some of the ways to test external interface calls using gock Mock in a table-driven way. And how to mock test GORM, after learning the basics of this article, I will write a separate article for you to introduce.

This is the third chapter of the Go language unit test series, which introduces how to use itgo-sqlmockandminiredisToolsMySQLandRedisofmocktest.

In the previous article"Go Unit Test-Simulate Service Request and Interface Return"In this article, we introduce how to use httptest and gock tools for network testing.

In addition to network dependencies, we often use various databases in development, such as the common MySQL and Redis. This article will give examples to demonstrate how to mock MySQL and Redis when writing unit tests.

go-sqlmock

sqlmock is an implementationsql/drivermock library. It does not require a real database connection to simulate the behavior of any sql driver in tests. Using it can be convenient to execute the result of the mock sql statement when writing unit tests.

Install

go get /DATA-DOG/go-sqlmock

Example of usage

The one used here isgo-sqlmockBasic sample code provided in the official documentation. In the following code, we implement arecordStatsFunctions are used to record relevant data generated by users when browsing products. The specific implementation function is to perform the following two SQL operations in a transaction:

  • +1 view times of the current product in the table
  • existproduct_viewersThe user ID of the current product is recorded in the table
// 
package main
import "database/sql"
// recordStats Record user browsing product informationfunc recordStats(db *, userID, productID int64) (err error) {
 // Start the transaction // Operate two tables of views and product_viewers tx, err := ()
 if err != nil {
  return
 }
 defer func() {
  switch err {
  case nil:
   err = ()
  default:
   ()
  }
 }()
 // Update the products table if _, err = ("UPDATE products SET views = views + 1"); err != nil {
  return
 }
 // Insert a data into the product_viewers table if _, err = (
  "INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)",
  userID, productID); err != nil {
  return
 }
 return
}
func main() {
 // Note: There is no real connection required during the test process db, err := ("mysql", "root@/blog")
 if err != nil {
  panic(err)
 }
 defer ()
 // Users with userID 1 have browsed products with productID 5 if err = recordStats(db, 1 /*some user id*/, 5 /*some product id*/); err != nil {
  panic(err)
 }
}

Now we need torecordStatsFunctions write unit tests, but do not want to connect to real databases for testing during the test. At this time, we can use it like in the following example codesqlmockTools to mock database operations.

package main
import (
 "fmt"
 "testing"
 "/DATA-DOG/go-sqlmock"
)
// TestShouldUpdateStats Test case for successful execution of sqlfunc TestShouldUpdateStats(t *) {
 // mock a * object, no need to connect to the real database db, mock, err := ()
 if err != nil {
  ("an error '%s' was not expected when opening a stub database connection", err)
 }
 defer ()
 // The return result when mock executes the specified SQL statement ()
 ("UPDATE products").WillReturnResult((1, 1))
 ("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult((1, 1))
 ()
 // Pass the mock DB object into our function if err = recordStats(db, 2, 3); err != nil {
  ("error was not expected while updating stats: %s", err)
 }
 // Ensure that all expected results are met if err := (); err != nil {
  ("there were unfulfilled expectations: %s", err)
 }
}
// TestShouldRollbackStatUpdatesOnFailure Test case for rollback failed to sql executionfunc TestShouldRollbackStatUpdatesOnFailure(t *) {
 db, mock, err := ()
 if err != nil {
  ("an error '%s' was not expected when opening a stub database connection", err)
 }
 defer ()
 ()
 ("UPDATE products").WillReturnResult((1, 1))
 ("INSERT INTO product_viewers").
  WithArgs(2, 3).
  WillReturnError(("some error"))
 ()
 // now we execute our method
 if err = recordStats(db, 2, 3); err == nil {
  ("was expecting an error, but there was none")
 }
 // we make sure that all expectations were met
 if err := (); err != nil {
  ("there were unfulfilled expectations: %s", err)
 }
}

In the above code, a test case with successful execution and a test case with failed execution rollback are defined, ensuring that every logical branch in our code can be tested, improving unit test coverage while also ensuring the robustness of the code.

Perform unit tests and see the final test results.

❯ go test -v
=== RUN   TestShouldUpdateStats
--- PASS: TestShouldUpdateStats (0.00s)
=== RUN   TestShouldRollbackStatUpdatesOnFailure
--- PASS: TestShouldRollbackStatUpdatesOnFailure (0.00s)
PASS
ok      golang-unit-test-demo/sqlmock_demo      0.011s

You can see that the results of both test cases are in line with expectations and the unit test passes.

In many scenarios where ORM tools are used, you can also use itgo-sqlmockThe library mock database operation is tested.

miniredis

In addition to using MySQL frequently, Redis is also used frequently in daily development. In the next section, we will learn how to mock Redis in unit tests together.

miniredis is a pure Go implementation of redis server for unit testing. It is a simple and easy to use, memory-based redis alternative, with a real TCP interface that you can think of as a redis versionnet/http/httptest

We can use it to mock the Redis operation when we write unit tests for some code that contains Redis operations.

Install

go get /alicebob/miniredis/v2

Example of usage

Here/go-redis/redisAs an example, a library has written a Redis operation containing several Redis operations.DoSomethingWithRedisfunction.

// redis_op.go
package miniredis_demo
import (
 "context"
 "/go-redis/redis/v8" // Pay attention to import the version "strings"
 "time"
)
const (
 KeyValidWebsite = "app:valid:website:list"
)
func DoSomethingWithRedis(rdb *, key string) bool {
 // This can be some logic for redis operations ctx := ()
 if !(ctx, KeyValidWebsite, key).Val() {
  return false
 }
 val, err := (ctx, key).Result()
 if err != nil {
  return false
 }
 if !(val, "https://") {
  val = "https://" + val
 }
 //Settings blog key five seconds expired if err := (ctx, "blog", val, 5*).Err(); err != nil {
  return false
 }
 return true
}

The following code is what I useminiredisThe library isDoSomethingWithRedisUnit test code written by function, whereminiredisNot only supports the commonly used Redis operations of mock, it also provides many practical help functions, such as checking whether the value of the key is equal to the expected one.()and help check key expiration time()

// redis_op_test.go
package miniredis_demo
import (
 "/alicebob/miniredis/v2"
 "/go-redis/redis/v8"
 "testing"
 "time"
)
func TestDoSomethingWithRedis(t *) {
 // mock a redis server s, err := ()
 if err != nil {
  panic(err)
 }
 defer ()
 // Prepare data ("q1mi", "")
 (KeyValidWebsite, "q1mi")
 // Redis server connecting to mock rdb := (&{
  Addr: (), //   mock redis server's address })
 // Call the function ok := DoSomethingWithRedis(rdb, "q1mi")
 if !ok {
  ()
 }
 // You can manually check whether the values ​​in redis are compounded with expectations if got, err := ("blog"); err != nil || got != "" {
  ("'blog' has the wrong value")
 }
 // You can also use the help tool to check (t, "blog", "")
 // Expired inspection (5 * ) // Fast forward 5 seconds if ("blog") {
  ("'blog' should not have existed anymore")
 }
}

Execute the test and view the unit test results:

❯ go test -v
=== RUN   ;TestDoSomethingWithRedis
--- PASS: TestDoSomethingWithRedis (0.00s)
PASS
ok      golang-unit-test-demo/miniredis_demo    0.052s

miniredisBasically, most Redis commands are supported, and you can learn more about the usage by checking the documentation.

Of course, except for usingminiredisIn addition to building a local redis server, you can also use various pile driving tools to pile specific methods. Which mock method is used when writing unit tests still depends on the actual situation.

Summarize

How to handle database dependencies when writing unit tests for code in daily work development is the most common problem, this article describes how to use itgo-sqlmockandminiredisTool mock-related dependencies.

Next, we will go further and introduce in detail how to implement the mock interface when writing unit tests. For more information about Go database CRUD Mock testing, please pay attention to my other related articles!