The project has been officially released, welcome to use!
gs-mock is a modern, type-safe Go mock library with full support for generics.
It addresses the shortcomings of traditional Go mock tools in terms of type safety and usability,
and natively supports concurrent testing through context.Context propagation.
gs-mock supports mocking the following targets:
- Interfaces (via code generation)
- Plain functions
- Struct methods
It is especially suitable for unit testing and component testing in microservice architectures.
-
Type Safety & Generics Support
- Native support for generic interfaces and generic functions
- Full type inference and auto-completion provided by IDEs
-
Multiple Parameters and Multiple Return Values
- Supports up to 7 parameters
- Supports up to 4 return values
- Covers the vast majority of real-world business function signatures
-
Handle Mode
- Encapsulates the entire mock logic in a single callback
- Suitable for scenarios with complex conditional logic
-
When / Return Mode
- Returns results based on condition matching
- Better suited for declarative and highly readable mock configurations
-
Interface Mocking
- Automatically generates mock implementations via code generation
- No reliance on
context.Contextparameter
-
Plain Function & Struct Method Mocking
- Propagates mock configuration through
context.Context - No need to introduce additional interfaces for functions or methods
- Propagates mock configuration through
-
Concurrent Testing Support
- Passes the Mock Manager via
context.Context - Ensures isolation and safety of mocks in concurrent test scenarios
- Passes the Mock Manager via
go install github.com/go-spring/gs-mock@latest
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/go-spring/gs/HEAD/install.sh)"
type Service interface {
Do(n int, s string) (int, error)
Format(s string, args ...any) string
}
//go:generate gs mock -o src_mock.go
gs mockindicates using themocksubcommand from thegstoolchain. You may also directly use thegs-mockcommand.
After adding the directive at the top of the interface file, mock code will be generated for all interfaces in the
current package. If you only want to generate mocks for specific interfaces, use the -i option:
//go:generate gs-mock -o src_mock.go -i '!RepositoryV2,Repository'
Examples of the -i option:
-i 'Repository'Generate a mock only for theRepositoryinterface-i '!Repository'Generate mocks for all interfaces exceptRepository-i 'Repository,Service'Generate mocks only forRepositoryandService-i '!Repository,Service'Generate mocks for all interfaces exceptRepository, but includeService
r := gsmock.NewManager()
s := NewServiceMockImpl(r)
// Handle mode: determine return logic based on input arguments
s.MockDo().Handle(func (n int, s string) (int, error) {
if n%2 == 0 {
return n * 2, nil
}
return n + 1, errors.New("error")
})
fmt.Println(s.Do(1, "abc")) // 2 error
fmt.Println(s.Do(2, "abc")) // 4 <nil>
r := gsmock.NewManager()
s := NewServiceMockImpl(r)
// For args[0] == "abc"; variadic arguments are represented as a slice
s.MockFormat().When(func (s string, args []any) bool {
return args[0] == "abc"
}).ReturnValue("abc")
// For args[0] == "123"; variadic arguments are represented as a slice
s.MockFormat().When(func (s string, args []any) bool {
return args[0] == "123"
}).ReturnValue("123")
fmt.Println(s.Format("", "abc", "123")) // abc
fmt.Println(s.Format("", "123", "abc")) // 123
fmt.Println(s.Format("", "xyz", "abc")) // panic: no matching mock found
Notes
- Do not mix
Handlemode andWhen/Returnmode on the same method- When multiple
When/Returnconfigurations exist, they are matched in registration order; the first successful match is executed
//go:noinline // Prevent the function from being inlined
func Do(ctx context.Context, n int) int { return n }
r := gsmock.NewManager()
ctx := gsmock.WithManager(context.TODO(), r)
// Return a fixed value using ReturnValue
gsmock.Func21(Do, r).ReturnValue(2)
fmt.Println(Do(ctx, 1)) // 2
Explanation:
-
Requirements for mocking plain functions:
- The first parameter must be
context.Context - Mock configuration is propagated via the context chain
- The first parameter must be
-
Func21means:- 2 parameters
- 1 return value
-
For variadic functions, use the
VarFuncNNseries, such asVarFunc21
type Service struct{ m int }
func (s *Service) Do(ctx context.Context, n int) int {
return n
}
r := gsmock.NewManager()
ctx := gsmock.WithManager(context.TODO(), r)
// The first parameter becomes the receiver type, followed by the original method parameters
gsmock.Func31((*Service).Do, r).Handle(func(s *Service, ctx context.Context, n int) int {
return n + s.m
})
fmt.Println((&Service{m: 1}).Do(ctx, 1)) // 2
fmt.Println((&Service{m: 2}).Do(ctx, 1)) // 3
Notes:
- Use a method expression (e.g.
(*Service).Do) instead of a method value (e.g.s.Do) - The receiver becomes the first parameter of the mock callback;
ctxbecomes the second parameter - Tests must be run with
-gcflags="all=-N -l"to prevent method inlining
More examples and usage can be found in the example directory.
-
Problem: In some cases, functions or methods are inlined by the Go compiler, causing mock logic not to be triggered.
-
Solution: Explicitly disable compiler optimizations when running tests:
go test -gcflags="all=-N -l" ./... -
Explanation: This disables inlining and certain optimizations, ensuring the mock framework can correctly intercept calls.
-
Problem: When mocking plain functions or struct methods, the first parameter must be
context.Context. -
Solution: When designing testable functions, include
context.Contextas the first parameter to allow mock manager propagation. -
Notes:
- This restriction only applies to plain functions and struct method mocking
- Interface mocks do not require
context.Contextin method signatures
-
Problem: When multiple
When/Returnrules are registered for the same method, matching order affects the outcome. -
Solution: The first successfully matched rule is executed, so register rules from most specific to most general.
-
Problem: The
Mock Manageritself is not goroutine-safe. Registering mocks dynamically during concurrent execution may cause unpredictable behavior. -
Solution: All mock registrations must be completed before any concurrent logic starts, and the manager should be passed to goroutines via
context.Context.
-
Problem: Variadic functions (e.g.
Printf(format string, args ...any)) have different parameter structures during mocking and require special handling. -
Solution: Use the
VarFuncNNseries to mock variadic functions. -
Explanation:
- Variadic arguments are wrapped as a single slice parameter in the mock callback
- All variadic mock types are prefixed with
Var
This project is licensed under the Apache License Version 2.0.