Day 3 - Testing the backend

Published by Michael Banzon on Wed Feb 15, 2017

Welcome to day three of out first full stack project (check out the project description if you haven’t)!

Today we are going to be testing the backend we coded on day two. First we need to formulate a solid test plan. This can get very academic - but don’t worry - that won’t happen today!

Purpose of testing

The purpose of testing is to ensure that everything functions as intended. We want to make sure that the backend coded is working properly and return correct answers to users. A pragmatic way to test this is to start up the server and fire a bunch of requests that we know the response to (or partly know) and check if the result is good.

Looking back at the definition of the API we did on day one there are three endpoints and two of them support two HTTP methods:

  • /currencies (GET, POST)
  • /convert (POST)
  • /webhook (POST, DELETE)

That leaves a total of five tests - plus the bootstrapping process etc.

Testing (in Go) comes with coverage reporting. This will be a guidance - but keep in mind that passed tests + full coverage doesn’t mean that everything will always function as intended. But it is a start!

How to test

Go has build in testing tools (go test) and these makes it very easy to write and execute tests. Files ending with _test.go are the ones included in the tests and test functions have the signature func(t *testing.T) - these will be executed in the order they appear in the code. The test files will not be included when the program is built.

The plan

Several parts is needed to test the currency server.

Using the built in testing in Go a small initialization is needed. Fortunately Go has the init() function that gets run before anything else - even when testing. In this case the init() function will be initializing the server and starting it - after this has been done the server can be tested.

The following snippet shows the initialization:

var (
	server   *Server
	runError error
)

func init() {
	var err error
	server, err = New()
	if err != nil {
		panic(err)
	}

	go func() {
		runError = server.Run()
		if runError != nil {
			panic(runError)
		}
	}()
}

Next we’ll add some functions to test the different endpoints.

Testing the initialization

The following snippet is a very short test function to ensure that everything has been set up correctly and is in fact running - note the loop with the time.Sleep call that waits increasing periods of time to let the server spin up. Every iteration checks the server.hasCurrencies to make the looping stop when the server is ready:

func TestServerCreation(t *testing.T) {
	if server == nil {
		t.Fatal("Server not initialized!")
	}

	if runError != nil {
		t.Fatal("Error starting the server!")
	}

	for loops := 0; !server.hasCurrencies && loops < 10; loops++ {
		t.Log("Sleep:", loops+1)
		time.Sleep(time.Duration(500*(loops+1)) * time.Millisecond)
	}

	if !server.hasCurrencies {
		t.Fatal("Currencies not loaded!")
	}
}

The test function in the snippet abose is placed in a file called a_test.go. The reason for the is that the go test tool will execute tests in alphabetical order - having a test file beginning with “a” will ensure it is run first.

Testing the rest of the server

The rest of the server is tested in a similar way using go test functionality.

To check out the specifics you can see the commit that added the tests.

Check out the output from the test:

➜  currencyconverter git:(master) go test -v -cover ./...
?   	github.com/goingfullstack/currencyconverter	[no test files]
=== RUN   TestServerCreation
2017/02/15 20:02:05 Starting server on 127.0.0.1:4000
2017/02/15 20:02:05 Starting currency fetching...
2017/02/15 20:02:05 Starting new currency fetch...
2017/02/15 20:02:05 Currencies updated.
2017/02/15 20:02:05 Sleeping 1h0m0s
--- PASS: TestServerCreation (0.50s)
	a_test.go:18: Sleep: 1
=== RUN   TestCurrencyConversionNewServer
--- PASS: TestCurrencyConversionNewServer (0.00s)
=== RUN   TestCurrencyConversion
--- PASS: TestCurrencyConversion (0.00s)
=== RUN   TestUnknownCurrency
--- PASS: TestUnknownCurrency (0.00s)
=== RUN   TestConvertResponseCreation
--- PASS: TestConvertResponseCreation (0.00s)
=== RUN   TestConvertedResponseUnknownCurrency
--- PASS: TestConvertedResponseUnknownCurrency (0.00s)
=== RUN   TestCreateCurrencyResponse
--- PASS: TestCreateCurrencyResponse (0.00s)
=== RUN   TestCreateInvalidCurrencyResponse
--- PASS: TestCreateInvalidCurrencyResponse (0.00s)
=== RUN   TestCurrencyGet
2017/02/15 20:02:05 [GET] /currencies
--- PASS: TestCurrencyGet (0.00s)
=== RUN   TestCurrencyPostKnownCurrency
2017/02/15 20:02:05 [POST] /currencies
--- PASS: TestCurrencyPostKnownCurrency (0.00s)
=== RUN   TestCurrencyPostInvalidRequest
2017/02/15 20:02:05 [POST] /currencies
2017/02/15 20:02:05 EOF
--- PASS: TestCurrencyPostInvalidRequest (0.00s)
=== RUN   TestCurrencyPostUnknownCurrency
2017/02/15 20:02:05 [POST] /currencies
2017/02/15 20:02:05 Unknown currency: FOO
--- PASS: TestCurrencyPostUnknownCurrency (0.00s)
=== RUN   TestCurrencyPut
2017/02/15 20:02:05 [PUT] /currencies
--- PASS: TestCurrencyPut (0.00s)
=== RUN   TestConvert
2017/02/15 20:02:05 [POST] /convert
--- PASS: TestConvert (0.00s)
=== RUN   TestConvertInvalidMethod
2017/02/15 20:02:05 [PUT] /convert
--- PASS: TestConvertInvalidMethod (0.00s)
=== RUN   TestConvertInvalidCurrency
2017/02/15 20:02:05 [POST] /convert
2017/02/15 20:02:05 Unknown currency: INVALID
--- PASS: TestConvertInvalidCurrency (0.00s)
=== RUN   TestConvertInvalidRequestData
2017/02/15 20:02:05 [POST] /convert
2017/02/15 20:02:05 EOF
--- PASS: TestConvertInvalidRequestData (0.00s)
=== RUN   TestNotFoundRoute
2017/02/15 20:02:05 [GET] /notfound
--- PASS: TestNotFoundRoute (0.00s)
=== RUN   TestWebhookRegister
2017/02/15 20:02:05 [POST] /webhook
2017/02/15 20:02:05 Webhook return code: 200
--- PASS: TestWebhookRegister (0.00s)
=== RUN   TestWebhookCalling
2017/02/15 20:02:05 Webhook return code: 200
--- PASS: TestWebhookCalling (0.00s)
=== RUN   TestWebhookWrongSecret
2017/02/15 20:02:05 [POST] /webhook
2017/02/15 20:02:05 Webhook return code: 403
--- PASS: TestWebhookWrongSecret (0.00s)
=== RUN   TestWebhookWrongBase
2017/02/15 20:02:05 [POST] /webhook
--- PASS: TestWebhookWrongBase (0.00s)
=== RUN   TestWebhookGet
2017/02/15 20:02:05 [GET] /webhook
--- PASS: TestWebhookGet (0.00s)
PASS
coverage: 85.1% of statements
ok  	github.com/goingfullstack/currencyconverter/server	0.524s	coverage: 85.1% of statements

As you see on the final line the test covers 85.1 percent of the code - it could be better but it’s pretty good so far.

What’s next?

Next we’ll set up the backend to run in production - the test we wrote today will be used when we automate the process of compiling and deploying to the server - but that is a bit later.

Continue to day four…