mirror of
https://github.com/wahyd4/go-mocket.git
synced 2026-08-09 04:46:54 +10:00
Fixed simple naming, comment conventions Fixed pointer references to a struc where there was also a pointer. Govet complain was right.
201 lines
5.9 KiB
Go
201 lines
5.9 KiB
Go
package gomocket
|
|
|
|
import (
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"fmt"
|
|
"log"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// DriverName is the name of the fake driver
|
|
DriverName = "MOCK_FAKE_DRIVER"
|
|
)
|
|
|
|
//Catcher is global instance of Catcher used for attaching all mocks to connection
|
|
var Catcher *MockCatcher
|
|
|
|
//MockCatcher is global entity to save all mocks aka FakeResponses
|
|
type MockCatcher struct {
|
|
Mocks []*FakeResponse // Slice of all mocks
|
|
Logging bool // Do we need to log what we catching?
|
|
PanicOnEmptyResponse bool // If not response matches - do we need to panic?
|
|
}
|
|
|
|
// Register safely register FakeDriver
|
|
func (mc *MockCatcher) Register() {
|
|
driversList := sql.Drivers()
|
|
for _, name := range driversList {
|
|
if name == DriverName {
|
|
return
|
|
}
|
|
}
|
|
sql.Register(DriverName, &FakeDriver{})
|
|
}
|
|
|
|
// Attach several mocks to MockCather. Could be useful to attach mocks from some factories of mocks
|
|
func (mc *MockCatcher) Attach(fr []*FakeResponse) {
|
|
mc.Mocks = append(mc.Mocks, fr...)
|
|
}
|
|
|
|
//FindResponse finds suitable response by provided
|
|
func (mc *MockCatcher) FindResponse(query string, args []driver.NamedValue) *FakeResponse {
|
|
if mc.Logging {
|
|
log.Printf("mock_catcher: check query: %s", query)
|
|
}
|
|
|
|
for _, resp := range mc.Mocks {
|
|
if resp.IsMatch(query, args) {
|
|
resp.MarkAsTriggered()
|
|
return resp
|
|
}
|
|
}
|
|
|
|
if mc.PanicOnEmptyResponse {
|
|
panic(fmt.Sprintf("No responses matches query %s ", query))
|
|
}
|
|
|
|
// Let's have always dummy version of response
|
|
return &FakeResponse{
|
|
Response: make([]map[string]interface{}, 0),
|
|
Exceptions: &Exceptions{},
|
|
}
|
|
}
|
|
|
|
//NewMock creates new FakeResponse and return for chains of attachments
|
|
func (mc *MockCatcher) NewMock() *FakeResponse {
|
|
fr := &FakeResponse{Exceptions: &Exceptions{}, Response: make([]map[string]interface{}, 0)}
|
|
mc.Mocks = append(mc.Mocks, fr)
|
|
return fr
|
|
}
|
|
|
|
//Reset removes all Mocks to start process again
|
|
func (mc *MockCatcher) Reset() *MockCatcher {
|
|
mc.Mocks = make([]*FakeResponse, 0)
|
|
return mc
|
|
}
|
|
|
|
//Exceptions represents possible exceptions during query executions
|
|
type Exceptions struct {
|
|
HookQueryBadConnection func() bool
|
|
HookExecBadConnection func() bool
|
|
}
|
|
|
|
//FakeResponse represents mock of response with holding all required values to return mocked response
|
|
type FakeResponse struct {
|
|
Pattern string // SQL query pattern to match with
|
|
Args []interface{} // List args to be matched with
|
|
Response []map[string]interface{} // Array of rows to be parsed as result
|
|
Once bool // To trigger only once
|
|
Triggered bool // If it was triggered at least once
|
|
Callback func(string, []driver.NamedValue) // Callback to execute when response triggered
|
|
RowsAffected int64 // Defines affected rows count
|
|
LastInsertID int64 // ID to be returned for INSERT queries
|
|
Error error // Any type of error which could happen dur
|
|
*Exceptions
|
|
}
|
|
|
|
// Returns true either when nothing to compare or deep equal check passed
|
|
func (fr *FakeResponse) isArgsMatch(args []driver.NamedValue) bool {
|
|
arguments := make([]interface{}, len(args))
|
|
if len(args) > 0 {
|
|
for index, arg := range args {
|
|
arguments[index] = arg.Value
|
|
}
|
|
}
|
|
return fr.Args == nil || reflect.DeepEqual(fr.Args, arguments)
|
|
}
|
|
|
|
func (fr *FakeResponse) isQueryMatch(query string) bool {
|
|
return fr.Pattern == "" || strings.Contains(query, fr.Pattern)
|
|
}
|
|
|
|
// IsMatch checks if both query and args matcher's return true and if this is Once mock
|
|
func (fr *FakeResponse) IsMatch(query string, args []driver.NamedValue) bool {
|
|
if fr.Once && fr.Triggered {
|
|
return false
|
|
}
|
|
return fr.isQueryMatch(query) && fr.isArgsMatch(args)
|
|
}
|
|
|
|
//MarkAsTriggered marks response as executed. For one time catches it will not make this possible to execute anymore
|
|
func (fr *FakeResponse) MarkAsTriggered() {
|
|
fr.Triggered = true
|
|
}
|
|
|
|
//WithQuery adds SQL query pattern to match for
|
|
func (fr *FakeResponse) WithQuery(query string) *FakeResponse {
|
|
fr.Pattern = query
|
|
return fr
|
|
}
|
|
|
|
// WithArgs attaches Args check for prepared statements
|
|
func (fr *FakeResponse) WithArgs(vars ...interface{}) *FakeResponse {
|
|
if len(vars) > 0 {
|
|
fr.Args = make([]interface{}, len(vars))
|
|
for index, v := range vars {
|
|
fr.Args[index] = v
|
|
}
|
|
}
|
|
return fr
|
|
}
|
|
|
|
// WithReply adds to chain and assign some parts of response
|
|
func (fr *FakeResponse) WithReply(response []map[string]interface{}) *FakeResponse {
|
|
fr.Response = response
|
|
return fr
|
|
}
|
|
|
|
// OneTime sets current mock to be triggered only once
|
|
func (fr *FakeResponse) OneTime() *FakeResponse {
|
|
fr.Once = true
|
|
return fr
|
|
}
|
|
|
|
// WithExecException says that if mock attached to non-SELECT query we need to trigger error there
|
|
func (fr *FakeResponse) WithExecException() *FakeResponse {
|
|
fr.Exceptions.HookExecBadConnection = func() bool {
|
|
return true
|
|
}
|
|
return fr
|
|
}
|
|
|
|
// WithQueryException adds to SELECT mocks triggering of error
|
|
func (fr *FakeResponse) WithQueryException() *FakeResponse {
|
|
fr.Exceptions.HookQueryBadConnection = func() bool {
|
|
return true
|
|
}
|
|
return fr
|
|
}
|
|
|
|
// WithCallback adds callback to be executed during matching
|
|
func (fr *FakeResponse) WithCallback(f func(string, []driver.NamedValue)) *FakeResponse {
|
|
fr.Callback = f
|
|
return fr
|
|
}
|
|
|
|
// WithRowsNum specifies how many records to consider as affected
|
|
func (fr *FakeResponse) WithRowsNum(num int64) *FakeResponse {
|
|
fr.RowsAffected = num
|
|
return fr
|
|
}
|
|
|
|
// WithID sets ID to be considered as insert ID for INSERT statements
|
|
func (fr *FakeResponse) WithID(id int64) *FakeResponse {
|
|
fr.LastInsertID = id
|
|
return fr
|
|
}
|
|
|
|
// WithError sets Error to FakeResponse struct to have it available on any statements executed
|
|
// example: WithError(sql.ErrNoRows)
|
|
func (fr *FakeResponse) WithError(err error) *FakeResponse {
|
|
fr.Error = err
|
|
return fr
|
|
}
|
|
|
|
func init() {
|
|
Catcher = &MockCatcher{}
|
|
}
|