Local Testing

While developing Sparta lambda functions it may be useful to test them locally without needing to provision each new code change. Sparta supports localhost testing in two different ways:

  • The explore command line option
  • httptest.NewServer for go test style testing

Example

For this example, let’s define a simple Sparta application:

package main

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/Sirupsen/logrus"
	sparta "github.com/mweagle/Sparta"
)

////////////////////////////////////////////////////////////////////////////////
// Hello world event handler
//
func helloWorld(event *json.RawMessage,
	context *sparta.LambdaContext,
	w http.ResponseWriter,
	logger *logrus.Logger) {

	fmt.Fprint(w, "Hello World")
}

////////////////////////////////////////////////////////////////////////////////
// Main
func main() {
	// Deploy it
	lambdaFn := sparta.NewLambda(sparta.IAMRoleDefinition{}, helloWorld, nil)
	var lambdaFunctions []*sparta.LambdaAWSInfo
	lambdaFunctions = append(lambdaFunctions, lambdaFn)

	sparta.Main("SpartaExplore",
		fmt.Sprintf("Test explore command"),
		lambdaFunctions,
		nil,
		nil)
}

Command Line Testing

With our application defined, let’s run it:

go run main.go explore

INFO[0000] Welcome to Sparta                             Option=explore TS=2016-01-31T18:05:19Z Version=0.3.0
INFO[0000] --------------------------------------------------------------------------------
INFO[0000] The following URLs are available for testing.
INFO[0000] main.helloWorld                               URL=http://localhost:9999/main.helloWorld
INFO[0000] Functions can be invoked via application/json over POST
INFO[0000] 	curl -vs -X POST -H "Content-Type: application/json" --data @testEvent.json http://localhost:9999/main.helloWorld
INFO[0000] Where @testEvent.json is a local file with top level `context` and `event` properties:
INFO[0000] 	{context: {}, event: {}}
INFO[0000] Starting Sparta server                        URL=http://localhost:9999

The localhost server mirrors the contract between the NodeJS proxying tier and the Go binary that is used in the AWS Lambda execution environment.

Per the instructions, let’s create a testEvent.json file with the required structure:

{
  "context" : {},
  "event" : {}
}

and post it:

curl -vs -X POST -H "Content-Type: application/json" --data @testEvent.json http://localhost:9999/main.helloWorld

*   Trying ::1...
* Connected to localhost (::1) port 9999 (#0)
> POST /main.helloWorld HTTP/1.1
> Host: localhost:9999
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 33
>
* upload completely sent off: 33 out of 33 bytes
< HTTP/1.1 200 OK
< Date: Mon, 21 Dec 2015 03:13:33 GMT
< Content-Length: 11
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
Hello World

Our lambda function (which at this point is just an HTTP handler) was successfully called and responded successfully.

While this approach does work, it’s not a scalable approach to writing automated tests.

httptest support

To integrate with the existing go test command, Sparta includes two functions:

  1. NewLambdaHTTPHandler : Creates an httptest.NewServer-compliant http.Handler value.
  2. Sparta/explore.NewRequest : Creates a mock JSON object with optional user-defined event data.

To show this in action, let’s walk through how Sparta does this:

func TestExplore(t *testing.T) {
	// 1. Create the function(s) we want to test
	var lambdaFunctions []*LambdaAWSInfo
	lambdaFn := NewLambda(IAMRoleDefinition{}, exploreTestHelloWorld, nil)
	lambdaFunctions = append(lambdaFunctions, lambdaFn)

	// 2. Mock event specific data to send to the lambda function
	eventData := ArbitraryJSONObject{
		"key1": "value1",
		"key2": "value2",
		"key3": "value3"}

	// 3. Make the request and confirm
	logger, _ := NewLogger("warning")
	ts := httptest.NewServer(NewLambdaHTTPHandler(lambdaFunctions, logger))
	defer ts.Close()
	resp, err := explore.NewRequest(lambdaFn.URLPath(), eventData, ts.URL)
	if err != nil {
		t.Fatal(err.Error())
	}

	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)

	t.Log("Status: ", resp.Status)
	t.Log("Headers: ", resp.Header)
	t.Log("Body: ", string(body))
}

The test function above has the following features:

  1. Create a slice of one or more Sparta lambda functions to test
  2. Optionally define event data to use in the test
  3. Create a new httptest.NewServer
  4. Issue the test case request with Sparta/explore.NewRequest
  5. Validate the test results

These localhost tests will be executed as part of your application’s normal go test lifecycle.

Notes