Skip to content

Instantly share code, notes, and snippets.

@Nydan
Last active April 15, 2021 08:51
Show Gist options
  • Save Nydan/2dada664e397d701c4a2be37533b95b2 to your computer and use it in GitHub Desktop.
Save Nydan/2dada664e397d701c4a2be37533b95b2 to your computer and use it in GitHub Desktop.
Code snippet for article: Structuring Integration Testing in Golang
package config
type Config struct {
ConnPsql string
}
// Load loads a configuration files into Config struct
func Load(path string) (Config, error) {
return Config{}, nil
}
// +build it
package it
import (
"database/sql"
"io/ioutil"
"net/http"
"github.com/nydan/integration_test/config"
"github.com/nydan/integration_test/migrator"
"github.com/nydan/integration_test/server"
"github.com/stretchr/testify/suite"
)
// exampleTestSuite is the basic testing suite.
// Here we can place all the dependency that we need for running the test.
// In this example we can add database client connection to test cases with DB.
type exampleTestSuite struct {
suite.Suite
db *sql.DB
cfg config.Config
srv *http.Server
}
// SetupTest initialize the testing suite dependency before the first test case executed.
// Within this step your integration test able to connect to any integrated client required
// For multiple test cases in a test suite, you may only need to initialize it once.
func (e *exampleTestSuite) SetupTest() {
// load configuration for your application
cfg, err := config.Load("application_cfg.toml")
e.NoError(err, "failed to load config")
e.cfg = cfg
// open connection to the database
db, err := sql.Open("postgres", e.cfg.ConnPsql)
e.NoError(err, "failed connect to DB")
e.NoError(db.Ping())
e.db = db
// migrate the database schema
err = migrator.Up("/migrations")
e.NoError(err, "failed to migrate up the db schema")
// setup an http server and router
e.srv = server.NewHTTPServer()
go func() {
err := e.srv.ListenAndServe()
e.NoError(err, "failed to start HTTP server")
}()
}
// TearDownSuite is tearing down all the setup that has been initialize in the SetupTest.
// This tear down only happen at the end of suite life cycle.
func (e *exampleTestSuite) TearDownSuite() {
var err error
err = e.db.Close()
e.NoError(err, "failed to close db connection")
err = e.srv.Close()
e.NoError(err, "failed to shutdown HTTP server")
}
// successLoginTest contains context for the test case. The value of each field is
// initialize on setup() and will be the reference for clean up the data on clean().
type successLoginTest struct {
id int64
email string
pass string
}
// createVerifiedUser creates a user to be tested for login API.
func (s *successLoginTest) createVerifiedUser(e *exampleTestSuite) {
row := e.db.QueryRow(
`INSERT INTO admin (email, password, status) VALUES ($1, $2, $3) returning id`,
s.email, s.pass, "verified")
err := row.Scan(&s.id)
e.NoError(err, "failed to insert user")
}
func (s *successLoginTest) loginAPICall(e *exampleTestSuite) *http.Response {
c := http.Client{}
req, err := http.NewRequest(http.MethodPost, "http://localhost:8080/login", nil)
e.NoError(err, "failed to create a request")
resp, err := c.Do(req)
e.NoError(err, "failed to do request")
return resp
}
// clean cleans predefined data or produced data from the test case.
func (s *successLoginTest) clean(e *exampleTestSuite) {
_, err := e.db.Exec(`DELETE FROM admin WHERE id = $1`, s.id)
e.NoError(err, "failed to delete user")
}
func (s *successLoginTest) assertLoginSuccessful(e *exampleTestSuite, resp *http.Response) {
buf, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
e.NoError(err, "failed to read response body")
// Do all the assertion related to the response body.
e.NotEmpty(buf)
}
// TestSuccessLogin test case that simulate a registered user try to
// login into your app. This test case specifically test your login scenario,
// and to test that particular part means we need have a predefined data
// which is a verified account.
func (e *exampleTestSuite) TestSuccessLogin() {
tt := new(successLoginTest)
defer tt.clean(e)
tt.createVerifiedUser(e)
resp := tt.loginAPICall(e)
tt.assertLoginSuccessful(e, resp)
}
package migrator
// Up migrate up the sql file from the path
func Up(path string) error {
return nil
}
// Down migrate down the sql file from the path
func Down(path string) error {
return nil
}
package server
import "net/http"
// NewHTTPServer create a new http Server instance
func NewHTTPServer() *http.Server {
return &http.Server{}
}
// exampleTestSuite is the basic testing suite.
// Here we can place all the dependency that we need for running the test.
// In this example we can add database client connection to test cases with DB.
type exampleTestSuite struct {
suite.Suite
db *sql.DB
cfg config.Config
srv *http.Server
}
// SetupTest initialize the testing suite dependency before the first test case executed.
// Within this step your integration test able to connect to any integrated client required
// For multiple test cases in a test suite, you may only need to initialize it once.
func (e *exampleTestSuite) SetupTest() {
// load configuration for your application
cfg, err := config.Load("application_cfg.toml")
e.NoError(err, "failed to load config")
e.cfg = cfg
// open connection to the database
db, err := sql.Open("postgres", e.cfg.ConnPsql)
e.NoError(err, "failed connect to DB")
e.NoError(db.Ping())
e.db = db
// migrate the database schema
err = migrator.Up("/migrations")
e.NoError(err, "failed to migrate up the db schema")
// setup an http server and router
e.srv = server.NewHTTPServer()
go func() {
err := e.srv.ListenAndServe()
e.NoError(err, "failed to start HTTP server")
}()
}
// TearDownSuite is tearing down all the setup that has been initialize in the SetupTest.
// This tear down only happen at the end of suite life cycle.
func (e *exampleTestSuite) TearDownSuite() {
var err error
err = e.db.Close()
e.NoError(err, "failed to close db connection")
err = e.srv.Close()
e.NoError(err, "failed to shutdown HTTP server")
}
// successLoginTest contains context for the test case. The value of each field is
// initialize on setup() and will be the reference for clean up the data on clean().
type successLoginTest struct {
id int64
email string
pass string
}
// createVerifiedUser creates a user to be tested for login API.
func (s *successLoginTest) createVerifiedUser(e *exampleTestSuite) {
row := e.db.QueryRow(
`INSERT INTO admin (email, password, status) VALUES ($1, $2, $3) returning id`,
s.email, s.pass, "verified")
err := row.Scan(&s.id)
e.NoError(err, "failed to insert user")
}
func (s *successLoginTest) loginAPICall(e *exampleTestSuite) *http.Response {
c := http.Client{}
req, err := http.NewRequest(http.MethodPost, "http://localhost:8080/login", nil)
e.NoError(err, "failed to create a request")
resp, err := c.Do(req)
e.NoError(err, "failed to do request")
return resp
}
// clean cleans predefined data or produced data from the test case.
func (s *successLoginTest) clean(e *exampleTestSuite) {
_, err := e.db.Exec(`DELETE FROM admin WHERE id = $1`, s.id)
e.NoError(err, "failed to delete user")
}
func (s *successLoginTest) assertLoginSuccessful(e *exampleTestSuite, resp *http.Response) {
buf, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
e.NoError(err, "failed to read response body")
// Do all the assertion related to the response body.
e.NotEmpty(buf)
}
// TestSuccessLogin test case that simulate a registered user try to
// login into your app. This test case specifically test your login scenario,
// and to test that particular part means we need have a predefined data
// which is a verified account.
func (e *exampleTestSuite) TestSuccessLogin() {
tt := new(successLoginTest)
defer tt.clean(e)
tt.createVerifiedUser(e)
resp := tt.loginAPICall(e)
tt.assertLoginSuccessful(e, resp)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment