-
Notifications
You must be signed in to change notification settings - Fork 808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Local Testing: How to mock a connection #616
Comments
You can use pgmock to create a mock server that pgx can connect to. It's fairly low level. It basically requires specifying what you want to happen at the wire protocol level. A higher level layer could be built on top of it... If your looking for more of a fake *pgx.Conn that doesn't have an underlying connection then that doesn't exist out of the box. But database/sql and pgx v3, the pgx v4 Rows type is an interface instead of a pointer. This was done specifically to allow for mocking the entire database connection. The way to do that would be for your application to work with interfaces instead of an actual *pgx.Conn. Then you could pass in objects that responded to Query and Exec however you wanted. So to summarize, the low level plumbing is there to do this at the wire level or at the connection handle level, but there are no high level mocking tools in pgx. |
Okay thanks for the response. However I don't quite see how the |
How it helps is that you can make an entire mock connection. If Here's a bigger picture explanation. Your code that is using a DB connection probably shouldn't be using a reference to a Conn or a Pool. Instead define your own interface with the subset of methods you use. This is what I have in one of my projects: type dbconn interface {
Begin(ctx context.Context) (pgx.Tx, error)
Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error)
Query(ctx context.Context, sql string, optionsAndArgs ...interface{}) (pgx.Rows, error)
QueryRow(ctx context.Context, sql string, optionsAndArgs ...interface{}) pgx.Row
} Then I have a method that uses the database. e.g. func CreateBook(ctx context.Context, db dbconn, book Book) (*Book, error) {
// ...
} If I wanted to test You'll have to build those mock implementations of As another benefit to implementing your application in terms of something like the |
This would be a really useful thing to add to the documentation. |
Thanks Jack for all your work. Would you have a small example on how to create the mock objet you are talking about ? I understand the interface part but don't really know where to go next. |
A goal for Mocking the |
Below is an approach I took to mocking out the pgx layer and gaining control of the methods in case others come to this and are thinking of ways to mock out pgx. Main packagepackage main
import (
"context"
"fmt"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
"log"
"os"
)
// This interface is created to allow us to override it in the test.
type dbOperations interface {
QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row
}
var conn dbOperations = nil
type Credentials struct {
Username string
Password string
}
var findUserByUsernameDBQuery = "select username, password from schema.users where username = '%s'"
func FindUserByUsername(username string) *Credentials {
connectToDB()
creds := Credentials{}
userSearchString := fmt.Sprintf(findUserByUsernameDBQuery, username)
err := conn.QueryRow(context.Background(), userSearchString).Scan(&creds.Username, &creds.Password)
if err != nil {
log.Println(err.Error())
}
return &creds
}
// Implemented a singleton for the connection which comes in handy within the test
// In the test we will set the global var conn to a mocked connection and then when the
// FindUserByUsername() function calls connectToDB() it will return our mocked connection
func connectToDB() {
if conn != nil {
return
}
dbConn, dbErr := pgxpool.Connect(context.Background(), "conn_string_here")
if dbErr != nil {
log.Println("Failed to connect to DB")
os.Exit(1)
}
//dbConn which is of type *pgxpool.Pool can be assigned to type dbOperations because at a minimum
//it implements the one method we have inside our dbOperations interface.
conn = dbConn
return
} Testing packagepackage main
import (
"context"
"github.com/jackc/pgx/v4"
"testing"
)
type mockDBConnection struct {
}
type mockRow struct {
username string
password string
}
func (this mockRow) Scan(dest ...interface{}) error {
username := dest[0].(*string)
password := dest[1].(*string)
*username = this.username
*password = this.password
return nil
}
func (this mockDBConnection) QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row {
return mockRow{"user1", "pass"}
}
func TestFindUserByUsernameReturnsUser(t *testing.T) {
// This assigns the conn global in the Main package to our mockedDBConnection allowing us to have
// full control over its functionality. When connectToDB() gets called our mockDBConnection is returned
conn = mockDBConnection{}
creds := FindUserByUsername("test1")
if creds.Username != "user1" && creds.Password != "pass" {
t.Errorf("Received wrong user: %s and pass: %s", creds.Username, creds.Password)
}
} A couple thoughts:
Two edits.
|
A follow up to my example as I literally ran in to an issue with the above not being flexible enough. Here is a bit more robust variation which allows each individual test to set the behavior of The below allows each test to define the functionality for the functions package auth
import (
"bytes"
"context"
"errors"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
"log"
"strings"
"testing"
)
// each mockDBConnection will implement its own queryRow
type MockQueryRow func(ctx context.Context, sql string, args ...interface{}) pgx.Row
type mockDBConnection struct {
mockQueryRow MockQueryRow
}
type Scan func(dest ...interface{}) error
type mockRow struct {
scan Scan
username string
password string
}
func (this mockRow) Scan(dest ...interface{}) error {
return this.scan(dest...)
}
func (this mockDBConnection) QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row {
return this.mockQueryRow(ctx, sql, args...)
}
func (this mockDBConnection) Begin(ctx context.Context) (pgx.Tx, error) {
return transaction{}, nil
}
func TestFindUserByUsernameReturnsUser(t *testing.T) {
mockScanReturn := func(dest ...interface{}) error {
username := dest[0].(*string)
password := dest[1].(*string)
*username = "user1"
*password = "pass"
return nil
}
mockQueryRowReturn := func(ctx context.Context, sql string, args ...interface{}) pgx.Row {
return mockRow{scan: mockScanReturn}
}
conn = mockDBConnection{mockQueryRow: mockQueryRowReturn}
creds := FindUserByUsername("test1")
if creds.Username != "user1" && creds.Password != "pass" {
t.Errorf("Received wrong user: %s and pass: %s", creds.Username, creds.Password)
}
}
func TestFindUserByUsernameLogsOnError(t *testing.T) {
mockScanReturn := func(dest ...interface{}) error {
return errors.New("empty user returned")
}
mockQueryRowReturn := func(ctx context.Context, sql string, args ...interface{}) pgx.Row {
return mockRow{scan: mockScanReturn}
}
conn = mockDBConnection{mockQueryRow: mockQueryRowReturn}
var logOutput bytes.Buffer
log.SetOutput(&logOutput)
_ = FindUserByUsername("t")
if !strings.Contains(logOutput.String(), "empty user returned") {
t.Errorf("Did not have error %s", logOutput.String())
}
} |
You can try my work on this: https://github.com/pashagolub/pgxmock |
@pashagolub Thank you very much. Works as intended! |
I know i am pretty late to this thread, however hoping to get some help since thread is still open :) So, seq of steps :
The problem i am facing now is i can mock the response of dbClient (this is the interface name where i have defined methods), however for tx.Commit() i am unable to. Note : i did noticed in the code that Tx has interfaces defined which i can leverage (i think) but trying to avoid it since doing that way exposes my code to possible failures if the interface contract changes. Also 1 month old to goLang so i maybe missing to provide some useful info, please do let me know if anything else is needed from my end. |
Update : i was able to mock the Tx.Commit() but the downside is that i had to mock all the methods defined in Tx interface to use the mock commit method. |
@Shiv-Singh33 Have you tried @pashagolub's https://github.com/pashagolub/pgxmock? |
@maxzaleski thanks for pointing me to the library, yes i did have a look at it, and it looks pretty cool and awesome!! |
"...i decided to do it manually" Famous last words, haha. Anyway, hope you find what you are looking for :) Best of luck! |
You guys also can take look at this repo, to mock pgxpools, it works for me: https://github.com/driftprogramming/pgxpoolmock
|
I write down some examples with different mock levels according to the discussions above, hope it helps😄 |
I have an application that I am trying to test where I am using a pgx connection. I would like to be able to inject a mock connection into my code where I can then expect/return expectations for that connection.
I tried to use https://github.com/DATA-DOG/go-sqlmock but pgx rejects it. I have looked at pgmock but that doesn't seem to do this at all.
Any ideas?
The text was updated successfully, but these errors were encountered: