Fixtures in Golang testing
Asked Answered
P

3

14

Coming from the python world, fixtures are very useful (Fixtures defines a Python contract for reusable state / support logic, primarily for unit testing). I was wondering if there's similar support in Golang which can allow me to run my tests with some predefined fixtures like setting up server, tearing it down, doing some repeated tasks each time a test is run ? Can someone point me to some examples of doing the same in Golang ?

Proudman answered 20/1, 2016 at 0:59 Comment(1)
Check out Ginkgo which has BeforeEach and AfterEach.Zeidman
E
9

If you want to use the standard Go testing tools, you can define a function with the signature TestMain(m *testing.M) and put your fixture code in there.

From the testing package wiki:

It is sometimes necessary for a test program to do extra setup or teardown before or after testing. It is also sometimes necessary for a test to control which code runs on the main thread. To support these and other cases, if a test file contains a function:

func TestMain(m *testing.M)

then the generated test will call TestMain(m) instead of running the tests directly. TestMain runs in the main goroutine and can do whatever setup and teardown is necessary around a call to m.Run. It should then call os.Exit with the result of m.Run. When TestMain is called, flag.Parse has not been run. If TestMain depends on command-line flags, including those of the testing package, it should call flag.Parse explicitly.

A simple implementation of TestMain is:

func TestMain(m *testing.M) {
    flag.Parse()
    os.Exit(m.Run())
}
Eisenhart answered 25/1, 2016 at 16:42 Comment(1)
Note you don't need flag.Parse() in there. It's only required if you need access to command line flags in your TestMain.Chesterfieldian
S
8

I know this is an old question, but this still came up in a search result so I thought I'd give a possible answer.

You can isolate code out to helper functions that return a "teardown" function to clean up after itself. Here's one possible way to go about starting a server and have it close at the end of the test case.

func setUpServer() (string, func()) {
    h := func(w http.ResponseWriter, r *http.Request) {
        code := http.StatusTeapot
        http.Error(w, http.StatusText(code), code)
    }

    ts := httptest.NewServer(http.HandlerFunc(h))
    return ts.URL, ts.Close
}

func TestWithServer(t *testing.T) {
    u, close := setUpServer()
    defer close()

    rsp, err := http.Get(u)
    assert.Nil(t, err)
    assert.Equal(t, http.StatusTeapot, rsp.StatusCode)
}

This starts up a server with net/http/httptest and returns its URL along with a function that acts as the "teardown." This function is added to the defer stack so it's always called regardless of how the test case exits.

Optionally, you can pass in the *testing.T if you have a more complicated set up going on in there and you need to handle errors. This example shows the set up function returning a *url.URL instead of a URL formatted string, and the parse can possibly return an error.

func setUpServer(t *testing.T) (*url.URL, func()) {
    h := func(w http.ResponseWriter, r *http.Request) {
        code := http.StatusTeapot
        http.Error(w, http.StatusText(code), code)
    }

    ts := httptest.NewServer(http.HandlerFunc(h))
    u, err := url.Parse(ts.URL)
    assert.Nil(t, err)
    return u, ts.Close
}

func TestWithServer(t *testing.T) {
    u, close := setUpServer(t)
    defer close()

    u.Path = "/a/b/c/d"
    rsp, err := http.Get(u.String())
    assert.Nil(t, err)
    assert.Equal(t, http.StatusTeapot, rsp.StatusCode)
}
Skerl answered 8/10, 2018 at 14:16 Comment(1)
Nice! I think this is about as similar as you can get to the pytest fixtures I'm used to.Ruggiero
M
-1

I wrote golang engine for use fixtures similar to pytest: https://github.com/rekby/fixenv

Usage example:

package example

// db create database abd db struct, cached per package - call
// once and same db shared with all tests
func db(e Env)*DB{...}

// DbCustomer - create customer with random personal data
// but fixed name. Fixture result shared by test and subtests, 
// then mean many calls Customer with same name will return same
// customer object.
// Call Customer with other name will create new customer
// and resurn other object.
func DbCustomer(e Env, name string) Customer {
    // ... create customer
    db(e).CustomerStore(cust)
    // ...
    return cust
}

// DbAccount create bank account for customer with given name.
func DbAccount(e Env, customerName, accountName string)Account{
    cust := DbCustomer(e, customerName)
    // ... create account
    db(e).AccountStore(acc)
    // ...
    return acc
}

func TestFirstOwnAccounts(t *testing.T){
    e := NewEnv(t)
    // background:
    // create database
    // create customer bob 
    // create account from
    accFrom := DbAccount(e, "bob", "from")
    
    // get existed db, get existed bob, create account to
    accTo := DbAccount(e, "bob", "to")
    
    PutMoney(accFrom, 100)
    SendMoney(accFrom, accTo, 20)
    if accFrom != 80 {
        t.Error()
    }
    if accTo != 20 {
        t.Error()   
    }
    
    // background:
    // delete account to
    // delete account from
    // delete customer bob
}

func TestSecondTransferBetweenCustomers(t *testing.T){
    e := NewEnv(t)
    
    // background:
    // get db, existed from prev test
    // create customer bob
    // create account main for bob
    accFrom := DbAccount(e, "bob", "main")
    
    // background:
    // get existed db
    // create customer alice
    // create account main for alice
    accTo := DbAccount(e, "alice", "main")
    PutMoney(accFrom, 100)
    SendMoney(accFrom, accTo, 20)
    if accFrom != 80 {
        t.Error()
    }
    if accTo != 20 {
        t.Error()
    }
    
    // background:
    // remove account of alice
    // remove customer alice
    // remove account of bob
    // remove customer bob
}

// background:
// after all test finished drop database
Mlle answered 27/8, 2021 at 23:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.