Documentation
¶
Overview ¶
Package querytest turns a querytrace.Trace into a test spy: it records the SQL one operation actually issued, and this package asserts on it after the fact. Where a mock library makes you declare the queries up front and fails if they do not run, querytest works the other way around — run the code under a Trace, then ask what it did: a query budget, no writes, no N+1, a SELECT on a given table that ran exactly once, an INSERT before the COMMIT.
The entry point is Expect, a fluent assertion bound to a testing.TB:
trace := querytrace.New("GetUserPage")
ctx := querytrace.WithTrace(context.Background(), trace)
service.RenderUserPage(ctx, id)
querytest.Expect(t, trace).
MaxQueries(5).
NoWrites().
NoNPlusOne().
Query(querytest.Select(), querytest.Table("users")).Times(1)
Match values (Select, Table, Fingerprint, SqlContains, Caller, HasWhere, …) select the statements an assertion applies to; pass several and they must all hold. All, Any, and Not compose them. Count and Events expose the same matching without a testing.TB, for inspecting a Trace directly.
Index ¶
- func AnyColumn() sqlpkg.Selection
- func AnyExpr() sqlpkg.Expression
- func AnyTable() sqlpkg.Source
- func Count(trace *querytrace.Trace, ms ...Match) int
- func Events(trace *querytrace.Trace, ms ...Match) []querytrace.Event
- type ArgMatcher
- type Assertion
- func (a *Assertion) AllRowsClosed() *Assertion
- func (a *Assertion) Committed() *Assertion
- func (a *Assertion) InOrder(ms ...Match) *Assertion
- func (a *Assertion) MaxQueries(n int) *Assertion
- func (a *Assertion) MinQueries(n int) *Assertion
- func (a *Assertion) NoNPlusOne() *Assertion
- func (a *Assertion) NoQueries() *Assertion
- func (a *Assertion) NoTransaction() *Assertion
- func (a *Assertion) NoWarn(code querytrace.WarningCode) *Assertion
- func (a *Assertion) NoWarnings() *Assertion
- func (a *Assertion) NoWrites() *Assertion
- func (a *Assertion) Queries(n int) *Assertion
- func (a *Assertion) Query(ms ...Match) *QueryAssertion
- func (a *Assertion) Reads(n int) *Assertion
- func (a *Assertion) RolledBack() *Assertion
- func (a *Assertion) Transactions(n int) *Assertion
- func (a *Assertion) UniqueQueries(n int) *Assertion
- func (a *Assertion) Warns(code querytrace.WarningCode) *Assertion
- func (a *Assertion) Writes(n int) *Assertion
- type Match
- func All(ms ...Match) Match
- func Any(ms ...Match) Match
- func Begin() Match
- func Caller(sub string) Match
- func Commit() Match
- func Ddl() Match
- func Delete() Match
- func Fingerprint(fp string) Match
- func HasJoin() Match
- func HasWhere() Match
- func Insert() Match
- func Label(key, value string) Match
- func Named(name string) Match
- func Not(m Match) Match
- func Op(k querytrace.StmtKind) Match
- func Params(n int) Match
- func ParamsAtLeast(n int) Match
- func Read() Match
- func Rollback() Match
- func Select() Match
- func Sql(text string) Match
- func SqlContains(sub string) Match
- func SqlMatches(re string) Match
- func Table(name string) Match
- func Update() Match
- func Write() Match
- type Matcher
- type Mock
- type Option
- type QueryAssertion
- type RowSet
- type Stub
- func (s *Stub) AnyTimes() *Stub
- func (s *Stub) MaxTimes(n int) *Stub
- func (s *Stub) MinTimes(n int) *Stub
- func (s *Stub) Required() *Stub
- func (s *Stub) Return(rows *RowSet) *Stub
- func (s *Stub) ReturnError(err error) *Stub
- func (s *Stub) ReturnResult(lastInsertID, rowsAffected int64) *Stub
- func (s *Stub) Times(n int) *Stub
- func (s *Stub) WillDelayFor(d time.Duration) *Stub
- func (s *Stub) WithArgs(args ...any) *Stub
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AnyColumn ¶
AnyColumn is a Select selection that matches any column or expression:
mock.ExpectQuery(sql.Select(querytest.AnyColumn()).From(sql.Tbl("users")))
func AnyExpr ¶
func AnyExpr() sqlpkg.Expression
AnyExpr is an expression that matches any expression, for Where and other clauses:
...Where(querytest.AnyExpr())
func AnyTable ¶
AnyTable is a From source that matches any table in a sql.Query expectation:
mock.ExpectQuery(sql.Select(sql.Col("id")).From(querytest.AnyTable()))
func Count ¶
func Count(trace *querytrace.Trace, ms ...Match) int
Count returns how many statements in trace match every Match in ms. It is the spy query underneath Query(...).Times, usable without a testing.TB.
func Events ¶
func Events(trace *querytrace.Trace, ms ...Match) []querytrace.Event
Events returns the statements in trace matching every Match in ms, in record order.
Types ¶
type ArgMatcher ¶
type ArgMatcher interface {
// MatchArg reports whether the executed bind value matches.
MatchArg(v any) bool
// String describes the matcher.
String() string
}
ArgMatcher decides whether the bind argument at a placeholder position matches. Place one in a sql.Query expectation with Arg, AnyArg, or ArgMatch — for example Where(Eq(Col("id"), querytest.Arg(5))) — and the mock checks the executed statement's argument there. Placeholders without an ArgMatcher (plain values) are left unconstrained, so a query matches any value unless you say otherwise.
func AnyArg ¶
func AnyArg() ArgMatcher
AnyArg matches any bind argument. It is explicit documentation that a value is deliberately unconstrained.
func Arg ¶
func Arg(v any) ArgMatcher
Arg matches a bind argument equal to v, comparing by database/sql driver value (so an int and the int64 the driver sees compare equal).
func ArgMatch ¶
func ArgMatch(fn func(v any) bool) ArgMatcher
ArgMatch matches a bind argument for which fn returns true.
type Assertion ¶
type Assertion struct {
// contains filtered or unexported fields
}
Assertion is a fluent set of checks against a single Trace. Each method reports a failure through the testing.TB and returns the Assertion so checks chain. Build one with Expect.
func Expect ¶
func Expect(t testing.TB, trace *querytrace.Trace) *Assertion
Expect snapshots trace and returns an Assertion over it. Call it once the code under test has run; the snapshot is stable, so later checks see the same statements and warnings even if the Trace keeps recording.
func (*Assertion) AllRowsClosed ¶
AllRowsClosed asserts no read left its row cursor open (no rows_not_closed warning).
func (*Assertion) InOrder ¶
InOrder asserts that statements matching ms appear in the trace in the given relative order: an event matching ms[0] before one matching ms[1], and so on. The matched events need not be adjacent, and matchers may be transaction matchers (Commit, Rollback, Begin), so Insert() before Commit() is expressible. Compose multi-condition steps with All.
func (*Assertion) MaxQueries ¶
MaxQueries asserts the trace recorded at most n statements, a query budget.
func (*Assertion) MinQueries ¶
MinQueries asserts the trace recorded at least n statements.
func (*Assertion) NoNPlusOne ¶
NoNPlusOne asserts the trace produced neither a possible-N+1 nor a query-in-loop warning.
func (*Assertion) NoQueries ¶
NoQueries asserts the trace touched the database not at all, the check for a path that should be served without a query (a cache hit).
func (*Assertion) NoTransaction ¶
NoTransaction asserts no transaction began within the trace.
func (*Assertion) NoWarn ¶
func (a *Assertion) NoWarn(code querytrace.WarningCode) *Assertion
NoWarn asserts the trace produced no warning with code.
func (*Assertion) NoWarnings ¶
NoWarnings asserts the trace produced no warning at all.
func (*Assertion) NoWrites ¶
NoWrites asserts the trace recorded no write statement, the check for a read-only code path.
func (*Assertion) Query ¶
func (a *Assertion) Query(ms ...Match) *QueryAssertion
Query narrows the assertion to the statements matching every Match in ms and returns a QueryAssertion to check how many ran. With no matchers it selects every statement.
func (*Assertion) RolledBack ¶
RolledBack asserts at least one transaction rolled back.
func (*Assertion) Transactions ¶
Transactions asserts exactly n transactions began within the trace.
func (*Assertion) UniqueQueries ¶
UniqueQueries asserts the trace recorded exactly n distinct query shapes (fingerprints).
func (*Assertion) Warns ¶
func (a *Assertion) Warns(code querytrace.WarningCode) *Assertion
Warns asserts the trace produced a warning with code.
type Match ¶
type Match struct {
// contains filtered or unexported fields
}
Match is a predicate over a recorded Event. Matchers select the statements an assertion or a spy query applies to; pass several to Query, Count, or Events and they must all hold (logical AND). All, Any, and Not compose them explicitly.
Most matchers look at the statement: Select/Insert/Update/Delete, Table, Fingerprint, HasWhere, Params, SqlContains. A few look at the surrounding Event (Named, Caller, Label, InTransaction) or the transaction timeline (Begin, Commit, Rollback). Matchers that depend on structural analysis (Table, HasWhere, Fingerprint) need a parser wired into the Config; without one they do not match, while operation and SQL-text matchers still do.
func Caller ¶
Caller matches a statement issued from application code whose function name or file path contains sub. It needs Config.CaptureCaller.
func Fingerprint ¶
Fingerprint matches a statement with the given normalized fingerprint.
func HasJoin ¶
func HasJoin() Match
HasJoin matches a statement that carries a JOIN clause (from analysis).
func HasWhere ¶
func HasWhere() Match
HasWhere matches a statement that carries a WHERE clause (from analysis).
func ParamsAtLeast ¶
ParamsAtLeast matches a statement carrying at least n bind arguments, the signature of a large IN list or batch.
func Sql ¶
Sql matches a statement whose raw SQL equals text, ignoring surrounding whitespace. It needs Config.CaptureRawSQL.
func SqlContains ¶
SqlContains matches a statement whose raw SQL contains sub, case-insensitively. It needs Config.CaptureRawSQL.
func SqlMatches ¶
SqlMatches matches a statement whose raw SQL matches the regular expression re. It panics if re does not compile. It needs Config.CaptureRawSQL.
type Matcher ¶
type Matcher interface {
// Matches reports whether a statement with the given SQL matches.
Matches(sql string) bool
// String describes the matcher for Verify failure messages.
String() string
}
Matcher decides whether a mock stub answers a statement, by its SQL text. The built-in matchers are Contains (a partial match), Regexp, Equals, and Anything; ExpectQuery and ExpectExec also accept a plain string or a sql.Query for convenience. Implement Matcher for custom matching.
It is distinct from the spy-side Match, which selects recorded statements by their analyzed shape; a Matcher sees only the raw SQL, at the point the mock must answer it.
type Mock ¶
type Mock struct {
// contains filtered or unexported fields
}
Mock is a programmable database/sql test double. It backs the *sql.DB returned by NewDB, answering each statement with a response you registered — canned rows, a result, or an error — so code that talks to a database can run in a unit test without one. Because the same DB is traced by querytrace, the spy assertions (Expect, Count, Events) see every statement the code issued, so the mock provides the inputs and the spy verifies the calls.
Register responses with ExpectQuery and ExpectExec. Each takes a Matcher that decides which statements the stub answers — Contains for a partial match, Regexp, or Equals — a plain string (matched with Equals), or a sql.Query built with the sqlkit builder. A sql.Query matches its compiled shape, and you may leave holes in it: AnyTable, AnyColumn, and AnyExpr match any table, column, or expression, while Arg, AnyArg, and ArgMatch constrain (or wildcard) a bind argument. Stubs are tried in registration order; the first applicable one answers. A statement matching no stub gets a lenient default — empty rows or a zero result — so a mock need only program the statements a test cares about. Verify reports any stub whose MinTimes (or Required) was not met.
func NewDB ¶
NewDB returns a *sql.DB backed by a programmable Mock and traced by querytrace, so statements run under a context carrying a Trace are recorded for the spy assertions. It registers a t.Cleanup that verifies the mock and closes the DB when the test ends, so a missed Required stub — or, by default, any statement no stub answered — fails the test without a manual Verify call. Pass Lenient to allow unmatched statements.
db, mock := querytest.NewDB(t)
mock.ExpectQuery(querytest.Contains("FROM users")).
Return(querytest.NewRows("id", "name").AddRow(1, "alice"))
trace := querytrace.New("LoadUser")
ctx := querytrace.WithTrace(context.Background(), trace)
// ... use db under ctx ...
querytest.Expect(t, trace).Query(querytest.Select(), querytest.Table("users")).Times(1)
func (*Mock) ExpectExec ¶
ExpectExec registers a response for ExecContext statements (db.Exec). See ExpectQuery for how the expectation is matched.
func (*Mock) ExpectQuery ¶
ExpectQuery registers a response for QueryContext statements (db.Query, db.QueryRow) whose SQL the matcher accepts. The matcher is a Matcher (Contains, Regexp, Equals, Anything), a plain string (matched with Equals), or a sql.Query (compiled with the mock's dialect, then Equals).
type Option ¶
type Option func(*config)
Option configures NewDB.
func Lenient ¶
func Lenient() Option
Lenient turns off strict verification: a statement that matches no stub is allowed (it gets the empty-rows / zero-result default) instead of failing the test at cleanup. Use it for code paths that deliberately issue statements the mock does not program.
func WithConfig ¶
func WithConfig(cfg querytrace.Config) Option
WithConfig sets the querytrace.Config the mock DB records with. The default is DebugConfig("postgres"), which captures raw SQL and callers so every matcher works; supply your own (for example with a parser wired in) to enable the structural matchers Table, HasWhere, and Fingerprint.
type QueryAssertion ¶
type QueryAssertion struct {
// contains filtered or unexported fields
}
QueryAssertion narrows an Assertion to the statements matching a set of matchers, ready to check how many of them ran. Build one with Assertion.Query; its terminal methods (Times, MinTimes, …) report the result and return the parent Assertion so checks keep chaining. The vocabulary mirrors the mock's Stub call-count methods.
func (*QueryAssertion) MaxTimes ¶
func (q *QueryAssertion) MaxTimes(n int) *Assertion
MaxTimes asserts at most n matching statements ran.
func (*QueryAssertion) MinTimes ¶
func (q *QueryAssertion) MinTimes(n int) *Assertion
MinTimes asserts at least n matching statements ran.
func (*QueryAssertion) Never ¶
func (q *QueryAssertion) Never() *Assertion
Never asserts no matching statement ran. It is shorthand for Times(0).
func (*QueryAssertion) Times ¶
func (q *QueryAssertion) Times(n int) *Assertion
Times asserts exactly n matching statements ran.
type RowSet ¶
type RowSet struct {
// contains filtered or unexported fields
}
RowSet is a set of canned rows a query stub returns. Build it with NewRows and add rows with AddRow.
type Stub ¶
type Stub struct {
// contains filtered or unexported fields
}
Stub is one programmed response. Build it with ExpectQuery or ExpectExec and set its reply with Return, ReturnResult, or ReturnError. The setters chain.
func (*Stub) AnyTimes ¶
AnyTimes lets the stub answer any number of statements, zero included, and imposes no Verify floor. It clears an earlier Times/MinTimes/MaxTimes/Required.
func (*Stub) MaxTimes ¶
MaxTimes caps how many statements the stub answers; further matching statements fall through to later stubs or the lenient default. Zero (the default) is unlimited.
func (*Stub) MinTimes ¶
MinTimes sets the fewest matching statements Verify requires. Zero (the default) requires none.
func (*Stub) Required ¶
Required marks the stub as one Verify must see matched at least once. It is shorthand for MinTimes(1).
func (*Stub) ReturnError ¶
ReturnError makes the stub answer with err.
func (*Stub) ReturnResult ¶
ReturnResult sets the result an exec stub answers with.
func (*Stub) Times ¶
Times sets the exact number of statements the stub answers and that Verify requires: shorthand for MinTimes(n) and MaxTimes(n).
func (*Stub) WillDelayFor ¶
WillDelayFor makes the stub wait d before answering, returning the context's error if it is canceled or its deadline passes first. It exercises slow-query and cancellation handling — querytrace records a mid-iteration cancellation as a warning.