• Stars
    star
    833
  • Rank 52,548 (Top 2 %)
  • Language
    Go
  • License
    Apache License 2.0
  • Created almost 4 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Mocking your SQL database in Go tests has never been easier.

copyist

Go Reference Latest Release License

Mocking your SQL database in Go tests has never been easier. The copyist library automatically records low-level SQL calls made during your tests. It then generates recording files that can be used to play back those calls without connecting to the real SQL database. Run your tests again. This time, they'll run much faster, because now they do not require a database connection.

Best of all, your tests will run as if your test database was reset to a clean, well-known state between every test case. Gone are the frustrating problems where a test runs fine in isolation, but fails when run in concert with other tests that modify the database. In fact, during playback you can run different test packages in parallel, since they will not conflict with one another at the database level.

copyist imposes no overhead on production code, and it requires almost no changes to your application or testing code, as long as that code directly or indirectly uses Go's sql package (e.g. Go ORM's and the widely used sqlx package). This is because copyist runs at the driver level of Go's sql package.

What problems does copyist solve?

Imagine you have some application code that opens a connection to a Postgres database and queries some customer data:

func QueryName(db *sql.DB) string {
	rows, _ := db.Query("SELECT name FROM customers WHERE id=$1", 100)
	defer rows.Close()

	for rows.Next() {
		var name string
		rows.Scan(&name)
		return name
	}
	return ""
}

The customary way to test this code would be to create a test database and populate it with test customer data. However, what if application code modifies rows in the database, like removing customers? If the above code runs on a modified database, it may not return the expected customer. Therefore, it's important to reset the state of the database between test cases so that tests behave predictably. But connecting to a database is slow. Running queries is slow. And resetting the state of an entire database between every test is really slow.

Various mocking libraries are another alternative to using a test database. These libraries intercept calls at some layer of the application or data access stack, and return canned responses without needing to touch the database. The problem with many of these libraries is that they require the developer to manually construct the canned responses, which is time-consuming and fragile when application changes occur.

How does copyist solve these problems?

copyist includes a Go sql package driver that records the low-level SQL calls made by application and test code. When a Go test using copyist is invoked with the "-record" command-line flag, then the copyist driver will record all SQL calls. When the test completes, copyist will generate a custom text file that contains the recorded SQL calls. The Go test can then be run again without the "-record" flag. This time the copyist driver will play back the recorded calls, without needing to access the database. The Go test is none the wiser, and runs as if it was using the database.

How do I use copyist?

Below is the recommended test pattern for using copyist. The example shows how to unit test the QueryName function shown above.

func init() {
	copyist.Register("postgres")
}

func TestQueryName(t *testing.T) {
	defer copyist.Open(t).Close()

	db, _ := sql.Open("copyist_postgres", "postgresql://root@localhost")
	defer db.Close()

	name := QueryName(db)
	if name != "Andy" {
		t.Error("failed test")
	}
}

In your init or TestMain function (or any other place that gets called before any of the tests), call the copyist.Register function. This function registers a new driver with Go's sql package with the name copyist_<driverName>. In any tests you'd like to record, add a defer copyist.Open(t).Close() statement. This statement begins a new recording session, and then generates a playback file when Close is called at the end of the test.

copyist does need to know whether to run in "recording" mode or "playback" mode. To make copyist run in "recording" mode, invoke the test with the record flag:

go test -run TestQueryName -record

This will generate a new recording file in a testdata subdirectory, with the same name as the test file, but with a .copyist extension. For example, if the test file is called app_test.go, then copyist will generate a testdata/app_test.copyist file containing the recording for the TestQueryName test. Now try running the test again without the record flag:

go test -run TestQueryName

It should now run significantly faster. You can also define the COPYIST_RECORD environment variable (to any value) to make copyist run in recording mode:

COPYIST_RECORD=1 go test ./...

This is useful when running many test packages, some of which may not link to the copyist library, and therefore do not define the record flag.

How do I reset the database between tests?

You can call SetSessionInit to register a function that will clean your database:

func init() {
    copyist.Register("postgres")
    copyist.SetSessionInit(resetDB)
}

The resetDB function will be called by copyist each time you call copyist.Open in your tests, as long as copyist is running in "recording" mode. The session initialization function can do anything it likes, but usually it will run a SQL script against the database in order to reset it to a clean state, by dropping/creating tables, deleting data from tables, and/or inserting "fixture" data into tables that makes testing more convenient.

Troubleshooting

I'm seeing "unexpected call" panics telling me to "regenerate recording"

This just means that you need to re-run your tests with the "-record" command line flag, in order to generate new recordings. Most likely, you changed either your application or your test code so that they call the database differently, using a different sequence or content of calls.

However, there are rarer cases where you've regenerated recordings, have made no test or application changes, and yet are still seeing this error when you run your tests in different orders. This is caused by non-determinism in either your application or in the ORM you're using.

As an example of non-determinism, some ORMs send a setup query to the database when the first connection is opened in order to determine the database version. So whichever test happens to run first records an extra Query call. If you run a different test first, you'll see the "unexpected call" error, since other tests aren't expecting the extra call.

The solution to these problems is to eliminate the non-determinism. For example, in the case of an ORM sending a setup query, you might initialize it from your TestMain method:

func TestMain(m *testing.M) {
	flag.Parse()
	copyist.Register("postgres")
	copyist.SetSessionInit(resetDB)
	closer := copyist.OpenNamed("test.copyist", "OpenCopyist")
	pop.Connect("copyist-test")
	closer.Close()
	os.Exit(m.Run())
}

This triggers the first query in TestMain, which is always run before tests.

The generated copyist recording files are too big

The size of the recording files is directly related to the number of accesses your tests make to the database, as well as the amount of data that they request. While copyist takes pains to generate efficient recording files that eliminate as much redundancy as possible, there's only so much it can do. Try to write tests that operate over smaller amounts of interesting data. For tests that require large numbers of database calls, or large amounts of data, use a different form of verification. One nice thing about copyist is that you can pick and choose which tests will use it. The right tool for the right job, and all that.

Limitations

  • Because of the way copyist works, it cannot be used with test and application code that accesses the database concurrently on multiple threads. This includes tests running with the "-parallel" testing flag, which enables tests in the same package to run in parallel. Multiple threads are problematic because the copyist driver code has no way to know which threads are associated with which tests. However, this limitation does not apply to running different test packages in parallel; in playback mode, this is both possible and highly encouraged! However, in recording mode, there may be problems if your tests conflict with one another at the database layer (i.e. by reading/modifying the same rows). The recommended pattern is to run test packages serially in recording mode, and then in parallel in playback mode.

  • copyist currently supports only the Postgres pq and pgx stdlib drivers. If you'd like to extend copyist to support other drivers, like MySql or SQLite, you're invited to submit a pull request.

  • copyist does not implement every sql package driver interface and method. This may mean that copyist may not fully work with some drivers with more advanced features. Contributions in this area are welcome.

More Repositories

1

cockroach

CockroachDB - the open source, cloud-native distributed SQL database.
Go
29,128
star
2

pebble

RocksDB/LevelDB inspired key-value database in Go
Go
4,455
star
3

errors

Go error library with error portability over the network
Go
1,869
star
4

apd

Arbitrary-precision decimals for Go
Go
540
star
5

cockroach-operator

k8s operator for CRDB
Go
264
star
6

docs

CockroachDB user documentation
HTML
177
star
7

django-cockroachdb

CockroachDB Backend for Django
Python
155
star
8

sqlalchemy-cockroachdb

SQLAlchemy adapter for CockroachDB
Python
135
star
9

activerecord-cockroachdb-adapter

CockroachDB adapter for ActiveRecord.
Ruby
100
star
10

rpc-bench

Benchmarking various RPC implementations
Go
87
star
11

examples-orms

Sample uses of CockroachDB with popular ORMs
Go
82
star
12

helm-charts

Helm charts for cockroachdb
Go
81
star
13

examples-go

Sample uses of cockroachDB.
Go
75
star
14

terraform-provider-cockroach

Terraform provider for CockroachDB Cloud
Go
53
star
15

cdc-sink

cdc-sink is a toolkit for ingesting logical replication feeds into a CockroachDB cluster
Go
52
star
16

sequelize-cockroachdb

Use Sequelize and CockroachDB together
JavaScript
52
star
17

cockroachdb-todo-apps

CockroachDB To-Do Apps
Python
51
star
18

c-rocksdb

🚫 DEPRECATED
C++
46
star
19

datadriven

Data-Driven Testing for Go
Go
38
star
20

logtags

key/value annotations for Go contexts
Go
35
star
21

cockroach-gen

CockroachDB with pre-generated Go code
Go
33
star
22

movr

A fictional ride sharing company.
Python
32
star
23

loadgen

CockroachDB load generators
Go
30
star
24

k8s

Images and utilities to run cockroach on kubernetes
Go
26
star
25

redact

Utilities to redact Go strings for confidentiality
Go
25
star
26

c-snappy

C++
21
star
27

ui

Published assets for Cockroach Labs user interfaces
TypeScript
19
star
28

cockroachdb-parser

Apache licensed CockroachDB parser and dependencies.
Go
19
star
29

roachprod

Internal CockroachDB production testing tool
Go
15
star
30

cockroachdb-typescript

A modern cloud-native web app using TypeScript, React, Prisma, Netlify serverless functions, and CockroachDB
TypeScript
14
star
31

crlfmt

Formatter for CockroachDB's additions to the Go style guide.
Go
14
star
32

homebrew-tap

`brew install cockroachdb/tap/cockroach`
Ruby
14
star
33

c-protobuf

🚫 DEPRECATED: go-gettable version of google/protobuf
C++
13
star
34

watermill-crdb

CockroachDB Pub/Sub for the Watermill project.
Go
13
star
35

cockroachdb-cloudformation

Quickly setup dev/test CockroachDB clusters using AWS CloudFormation and Kubernetes
Shell
12
star
36

molt

Migrate Off Legacy Things - open-source tooling to assist migrations to CockroachDB.
Go
12
star
37

vendored

CockroachDB's vendored Go dependencies
Go
11
star
38

c-jemalloc

🚫 DEPRECATED
C
11
star
39

yacc

Fork of go yacc tool with increased hardcoded constants to handle larger grammars.
Go
11
star
40

examples-python

Sample uses of CockroachDB
Python
10
star
41

c-lz4

C
10
star
42

build-cache

Cache the installed output of go builds
Go
9
star
43

definitive_guide_sample_code

Sample code for The Definitive Guide to CockroachDB
JavaScript
8
star
44

quickstart-code-samples

Java
8
star
45

pcf-crdb-service-broker

Pivotal Cloud Foundry service broker for CockroachDB.
Go
8
star
46

dcos-cockroachdb-service

Framework for running CockroachDB on Mesosphere DC/OS
Python
8
star
47

backport

automatically backport pull requests
Go
7
star
48

cockroach-proto

Protocol Buffer
7
star
49

python-heroku-cockroachdb

A modern cloud-native web app using Python, Flask, Heroku, and CockroachDB
Python
7
star
50

walkabout

Walkabout generates visitor-pattern accessors for your existing go structs.
Go
6
star
51

sample-apps

6
star
52

nextjs-react-vercel-crdb-app

A CockroachDB sample app for coordinating social events using Next.js with react-bootstrap deployed on vercel
JavaScript
6
star
53

rksql

Fork of scaledata/rksql
Go
5
star
54

ddshop

TodoMVC app for distributed database workshop
JavaScript
5
star
55

postgres-test

Dockerfile
5
star
56

benchviz

A tool used for visualizing results from benchmark tests over time.
Go
5
star
57

stress

A fork of Golang's stress function.
Go
5
star
58

cockroach-cloud-sdk-go

Go client SDK for CockroachDB Cloud
Mustache
5
star
59

example-app-rust-postgres

Rust
4
star
60

protoc-gen-gogoroach

protoc (protobuf compiler) plugin for CockroachDB protobuf files
Go
4
star
61

generated-diagrams

HTML
4
star
62

roachspeed

codespeed instance
Python
4
star
63

hibernate-savepoint-fix

Java
4
star
64

scripts

Utility and helper scripts for CockroachDB contributors -
Vim Script
4
star
65

go-lab

Scripts and test programs to reveal what happens under the hood in Go.
Go
4
star
66

yarn-vendored

CockroachDB's vendored JavaScript dependencies
3
star
67

cockroachdb.github.io

Supporting static content for CockroachDB
JavaScript
3
star
68

cgo_static_boom

cgo static binary boom
Go
3
star
69

docs-docker

Docker image for the docs build
3
star
70

libedit

CockroachDB fork of the libedit terminal line-editing library
3
star
71

gostdlib

Vendor-friendly Go standard library packages
Go
3
star
72

dev

A utility for performing common CockroachDB development tasks
3
star
73

example-app-python-psycopg3

Simple CRUD application in Python using the psycopg3 driver.
Python
3
star
74

scp-interview

Shell
3
star
75

efcore.pg.cockroach

C#
3
star
76

university-event-driven-architecture-for-java-developers-app-exercises

Exercise code for Cockroach University - Event Driven Architecture for Java Developers
Java
3
star
77

bincheck

Verify CockroachDB binaries
Shell
3
star
78

eddie

Big Eddie, the golang contract enforcer
Go
3
star
79

roachperf-og

🚫 Deprecated CockroachDB performance tool — use roachprod instead!
Go
3
star
80

university-getting-started-with-sql-app-exercises

Exercise code for the Cockroach University - Getting Started with SQL for Application Developers course
Shell
3
star
81

jsonb-spec

Acceptance tests/spec for CockroachDB JSONB support, complement to scoping RFC
JavaScript
3
star
82

social-events-app

JavaScript
3
star
83

ttycolor

Conditionally expose ANSI color codes for use on a terminal
Go
2
star
84

admin-ui-components

Shared frontend components between the CRDB Admin UI and other apps
TypeScript
2
star
85

rails-crdb-app

A CockroachDB leaderboard sample application using Rails deployed on Heroku
Ruby
2
star
86

fullstack-node-cockroachdb-app

Fullstack sample application in Node.js using Sequelize
JavaScript
2
star
87

university-multi-region-course-app-exercises

exercise code for the cockroach university multi-region course
Java
2
star
88

go-plus

API library for CRL's custom Go extensions
Go
2
star
89

tokenbucket

Token bucket implementation in Go
Go
2
star
90

homebrew-go

🚫 DEPRECATED 🚫 — use `brew install go` instead
Ruby
2
star
91

build-utils

🚫 DEPRECATED: Build utilities for CockroachDB
Go
2
star
92

psycopg2-cockroachdb

Testing psycopg2 against cockroachdb
Shell
2
star
93

katacoda

In-browser, interactive tutorials on CockroachDB
Shell
2
star
94

cockroachdb-hibernate

Package to improve compatibility between CockroachDB and Hibernate
2
star
95

pulumi-poc

Demo of simple control plane built on top of Pulumi.
Go
2
star
96

avrogen

Avro generator for import testing
Go
1
star
97

redcarpet-extender

Extends the Redcarpet Markdown parser to handle CockroachDB documentation
1
star
98

s3checker

Check s3 support for bulk operations
Go
1
star
99

university-getting-started-with-node-postgres-app-exercises

JavaScript
1
star
100

university-multi-region-latency-exercises

Optimizing latency in multi-region database course exercise code
Shell
1
star