Isolates dependencies in Go and Rust unit tests using test-double generation - Go: `go.uber.org/mock` (gomock + mockgen codegen) and `github.com/stretchr/testify/mock` (hand-written stubs); Rust: `mockall` crate with `#[automock]` for traits and `mock!` macro for structs and external traits. Use when a unit test reaches a database, HTTP client, file system, or any interface boundary that must be replaced with a controlled fake to keep tests fast, deterministic, and isolated.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-unit-tests-go-rust:go-rust-mockingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A test double (per [ISTQB Glossary v4.7.1][istqb-double]) is a component
A test double (per ISTQB Glossary v4.7.1) is a component that replaces a real dependency during testing so the subject under test can be exercised in isolation. This skill covers the two dominant double-generation approaches in the Go and Rust ecosystems.
| Ecosystem | Tool | Approach |
|---|---|---|
| Go | go.uber.org/mock (gomock + mockgen) | Codegen from interface |
| Go | github.com/stretchr/testify/mock | Hand-written stub struct |
| Rust | mockall | Attribute (#[automock]) or macro (mock!) |
For tests that do not cross an interface boundary, prefer real objects or simple stubs without a mocking library.
go get go.uber.org/mock/gomock
go install go.uber.org/mock/mockgen@latest
mockgen is the code generator. go.uber.org/mock/gomock is the runtime
package imported in tests.
Two modes. Source mode reads a .go file; package mode reflects the
installed package. Per pkg.go.dev/go.uber.org/mock/gomock:
# Source mode: generates from a .go file
mockgen -source=internal/store/store.go \
-destination=internal/store/mock_store.go \
-package=store
# Package mode: specifies package + interface names
mockgen github.com/myorg/myapp/internal/store Store,Querier \
> internal/store/mock_store.go
Commit generated files or regenerate in CI via go generate. Add a
//go:generate mockgen ... directive in the source file so go generate ./...
keeps mocks in sync.
The -typed flag (default false) emits type-safe Return/Do/DoAndReturn
helpers that avoid any casts in expectations.
Per pkg.go.dev/go.uber.org/mock/gomock:
func TestOrderService_Submit(t *testing.T) {
ctrl := gomock.NewController(t)
// ctrl.Finish() is called automatically via t.Cleanup when *testing.T
// is passed to NewController - no explicit call needed.
mockStore := store.NewMockStore(ctrl)
// Expect SaveOrder called once with any Order, return nil error.
mockStore.EXPECT().
SaveOrder(gomock.Any()).
Return(nil).
Times(1)
svc := NewOrderService(mockStore)
if err := svc.Submit(Order{ID: "abc"}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
Per pkg.go.dev/go.uber.org/mock/gomock:
| Matcher | Behaviour |
|---|---|
gomock.Any() | Accepts any argument value |
gomock.Eq(v) | Deep equality |
gomock.Nil() | Matches nil |
gomock.Not(m) | Negates matcher m |
gomock.AssignableToTypeOf(v) | Type-assignability check |
gomock.InAnyOrder(s) | Slice elements in any order |
gomock.Regex(re) | String matches regexp |
Per pkg.go.dev/go.uber.org/mock/gomock:
// Exact count
mockStore.EXPECT().FindByID(gomock.Any()).Return(nil, ErrNotFound).Times(2)
// At least / at most
mockStore.EXPECT().Ping().MinTimes(1).MaxTimes(3)
// Unbounded
mockStore.EXPECT().Metrics().AnyTimes()
// Strict ordering
gomock.InOrder(
mockStore.EXPECT().Begin(),
mockStore.EXPECT().SaveOrder(gomock.Any()).Return(nil),
mockStore.EXPECT().Commit(),
)
Per github.com/stretchr/testify and pkg.go.dev/github.com/stretchr/testify/mock:
go get github.com/stretchr/testify
Import path for the mock sub-package: github.com/stretchr/testify/mock.
Embed mock.Mock and implement the interface by hand. Each method calls
m.Called(...) to record the invocation and retrieve configured returns:
// Implements the Notifier interface.
type MockNotifier struct {
mock.Mock
}
func (m *MockNotifier) Send(to, body string) error {
args := m.Called(to, body)
return args.Error(0)
}
Per github.com/stretchr/testify:
func TestAlertService_Notify(t *testing.T) {
n := new(MockNotifier)
// On("MethodName", arg...).Return(values...)
n.On("Send", "[email protected]", mock.Anything).Return(nil)
svc := NewAlertService(n)
if err := svc.Notify("[email protected]", "disk full"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Asserts every On(...) expectation was exercised.
n.AssertExpectations(t)
}
mock.Anything matches any value for that argument position (equivalent
to gomock.Any()).
Additional assertion methods per pkg.go.dev/github.com/stretchr/testify/mock:
n.AssertCalled(t, "Send", "[email protected]", mock.Anything) - verifies
the call happened.n.AssertNotCalled(t, "Send") - verifies it never happened.| Concern | gomock | testify/mock |
|---|---|---|
| Mock generation | mockgen codegen | Hand-written |
| Argument matching | Rich matcher library | mock.Anything + basic |
| Strict call count | Times/MinTimes/MaxTimes | Times(n) on Call |
| Ordering | InOrder/After | Not built-in |
| Dependency | Two packages (gomock + mockgen) | One package |
Use gomock when strict call-order or exhaustive argument matching matters.
Use testify/mock when the team already uses testify/assert and wants a
single dependency.
Per docs.rs/mockall/latest/mockall:
# Cargo.toml
[dev-dependencies]
mockall = "0.14.0"
Two entry points: #[automock] for traits you own; mock! for structs or
traits defined in external crates.
Per docs.rs/mockall/latest/mockall, applying #[automock]
generates a MockTraitName struct in the same module:
use mockall::automock;
use mockall::predicate::*;
#[automock]
pub trait Cache {
fn get(&self, key: &str) -> Option<String>;
fn set(&mut self, key: &str, value: String);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lookup_hits_cache() {
let mut mock = MockCache::new();
mock.expect_get()
.with(eq("user:42"))
.times(1)
.returning(|_| Some("alice".to_string()));
let result = lookup(&mock, "user:42");
assert_eq!(result, Some("alice".to_string()));
}
}
Expectations are verified automatically when mock is dropped: if
times(1) is declared but the method is never called, the drop panics
and fails the test.
Per docs.rs/mockall/latest/mockall, use mock! when the
trait is defined in a dependency you cannot annotate:
use mockall::mock;
mock! {
pub HttpClient {}
impl reqwest_like::Client for HttpClient {
fn get(&self, url: &str) -> String;
fn post(&self, url: &str, body: &str) -> String;
}
}
#[test]
fn test_fetch_uses_get() {
let mut client = MockHttpClient::new();
client.expect_get()
.with(eq("https://api.example.com/v1/data"))
.times(1)
.return_once(|_| r#"{"ok":true}"#.to_string());
let result = fetch_data(&client);
assert!(result.contains("ok"));
}
Per docs.rs/mockall/latest/mockall:
| Method | Behaviour |
|---|---|
.times(n) | Requires exactly n calls |
.times(..) | Any number (range syntax) |
.with(matcher) | Argument predicate from mockall::predicate::* |
.returning(closure) | Computes return value via FnMut |
.return_once(closure) | Consumes an FnOnce (for non-Clone returns) |
.return_const(value) | Clones and returns a constant |
.never() | Asserts the method is never called |
Common predicates (mockall::predicate::*): eq(v), ne(v), lt(v),
gt(v), function(fn), always(), never().
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Mock every dependency | Tests verify mock wiring, not behavior | Mock only across true isolation boundaries |
Forget AssertExpectations (testify) | Uncalled On(...) expectations pass silently | Always call n.AssertExpectations(t) at end |
Check ctrl.Finish() manually (gomock) | Redundant; NewController(t) registers auto-cleanup | Remove manual Finish() calls |
AnyTimes() on every call (gomock) | Hides missing invocations | Use Times(1) or MinTimes(1) by default |
Forget #[cfg(test)] on mock module (Rust) | Mock types compiled into release binary | Wrap MockXxx usage in #[cfg(test)] |
mock! when #[automock] is enough (Rust) | Verbose boilerplate for owned traits | Use #[automock] for traits in your own crate |
go generate ./... in CI to catch drift.#[automock]: does not support some advanced generic trait
patterns; fall back to mock! with explicit type parameters.go.uber.org/mock READMEgomock package documentationstretchr/testify READMEtestify/mock package documentationmockall crate documentation on docs.rsgo-test - Go stdlib testing (prerequisite)cargo-test - Rust cargo testing (prerequisite)rstest-tests - Rust parametrized testsProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub testland/qa --plugin qa-unit-tests-go-rust