- 
                Notifications
    You must be signed in to change notification settings 
- Fork 556
Decorating tests to provide common logic
Sometimes a test suite has some specific resource or state which has to be set up and then torn down for every test. This is a way to simplify that process by making it possible to wrap tests in helper functions.
This is useful for otherwise shared common state, like database transactions, where you set up the transaction, let the test operate on it, check that it is still alive and finally perform a rollback before the next test.
The basic idea is this:
- Provide a function which performs the following:
- Accepts a closure fas a parameter which will be invoked with the initialized resource or state
- Creates and returns a func()which performs the following:- Initializes the resource
- Provides a Resetfor the resource, if needed
- Executes the closure f
 
 
- Accepts a closure 
- Then to use this function in tests:
- Create a closure which takes the initialized resource or state as a parameter
- Pass this closure to the created helper function
- Pass the result from the helper function as the block to a Conveyinvocation
 
Note: When reporting failures, the failure will be reported by traversing the stack trace looking for the first location in a file that ends with _test.go.  For this reason, it is important that calls to So appear in such a file.  Otherwise, the error will be reported at a different (probably higher) level in the stack and it will be unclear exactly what failure has occurred.
package main
import (
	"database/sql"
	"testing"
	_ "github.com/lib/pq"
	. "github.com/smartystreets/goconvey/convey"
)
func WithTransaction(db *sql.DB, f func(tx *sql.Tx)) func() {
	return func() {
		tx, err := db.Begin()
		So(err, ShouldBeNil)
		Reset(func() {
			/* Verify that the transaction is alive by executing a command */
			_, err := tx.Exec("SELECT 1")
			So(err, ShouldBeNil)
			tx.Rollback()
		})
		/* Here we invoke the actual test-closure and provide the transaction */
		f(tx)
	}
}
func TestUsers(t *testing.T) {
	db, err := sql.Open("postgres", "postgres://localhost?sslmode=disable")
	if err != nil {
		panic(err)
	}
	Convey("Given a user in the database", t, WithTransaction(db, func(tx *sql.Tx) {
		_, err := tx.Exec(`INSERT INTO "Users" ("id", "name") VALUES (1, 'Test User')`)
		So(err, ShouldBeNil)
		Convey("Attempting to retrieve the user should return the user", func() {
			 var name string
			 data := tx.QueryRow(`SELECT "name" FROM "Users" WHERE "id" = 1`)
			 err = data.Scan(&name)
			 So(err, ShouldBeNil)
			 So(name, ShouldEqual, "Test User")
		})
	}))
}
/* Required table to run the test:
CREATE TABLE "public"."Users" ( 
	"id" INTEGER NOT NULL UNIQUE, 
	"name" CHARACTER VARYING( 2044 ) NOT NULL
);
*/Output:
=== RUN TestUsers
  Given a user in the database ✔✔
    Attempting to retrieve the user should return the user ✔✔✔
--- PASS: TestUsers (0.01 seconds)
PASS
ok      test    0.022s