Unit testing a HTTP handler that returns a stream of multiple files - unit-testing

HTTP handler
I have a HTTP handler like this:
func RouteHandler(c echo.Context) error {
outs := make([]io.Reader, 5)
for i := range outs {
outs[i] = // ... comes from a logic.
}
return c.Stream(http.StatusOK, "application/binary", io.MultiReader(outs...))
}
Unit test
I intend to write a unit test for HTTP handler and investigate the returned stream of multiple files.
I have these helper type and function for my unit test:
type Handler func(echo.Context) error
// Send request to a handler. Get back response body.
func Send(req *http.Request, handler Handler) ([]byte, error) {
w := httptest.NewRecorder()
e := echo.New()
c := e.NewContext(req, w)
// Call the handler.
err := handler(c)
if err != nil {
return nil, err
}
res := w.Result()
defer res.Body.Close()
return ioutil.ReadAll(res.Body)
}
Then, I use the above type and function to send a request to my HTTP handler from within my unit test:
// From within my unit test:
// Initialize request...
var data []byte
data, err := Send(request, RouteHandler)
// How to separate the multiple files returned here?
// How to work with the returned data?
Work with returned stream of files
How can I separate multiple files returned by the HTTP handler? How can I work with the stream of data returned by HTTP handler?

... Possible options: write a length followed by the content of a file...
Actually, the above option commented by #CeriseLimón was already implemented and was used by the front-end.

Related

Making ioutil.ReadAll(response.Body) throw error in golang

For some reason, I cannot seem to get ioutil.ReadAll(res.Body), where res is the *http.Response returned by res, err := hc.Do(redirectRequest) (for hc http.Client, redirectRequest *http.Request).
Testing strategy thus far
Any time I see hc.Do or http.Request in the SUT, my instinct is to spin up a fake server and point the appropriate application states to it. Such a server, for this test, looks like this :
badServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// some stuff
w.Write([some bad bytes])
}))
defer badServer.Close()
I don't seem to have a way to control res.Body, which is literally the only thing keeping me from 100% test completion against the func this is all in.
I tried, in the errorThrowingServer's handler func, setting r.Body to a stub io.ReadCloser that throws an error when Read() is called, but that doesn't effect res.
You can mock the body. Basically body is an io.ReadCloser interface so, you can do something like this:
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type mockReadCloser struct {
mock.Mock
}
func (m *mockReadCloser) Read(p []byte) (n int, err error) {
args := m.Called(p)
return args.Int(0), args.Error(1)
}
func (m *mockReadCloser) Close() error {
args := m.Called()
return args.Error(0)
}
func TestTestingSomethingWithBodyError(t *testing.T) {
mockReadCloser := mockReadCloser{}
// if Read is called, it will return error
mockReadCloser.On("Read", mock.AnythingOfType("[]uint8")).Return(0, fmt.Errorf("error reading"))
// if Close is called, it will return error
mockReadCloser.On("Close").Return(fmt.Errorf("error closing"))
request := &http.Request{
// pass the mock address
Body: &mockReadCloser,
}
expected := "what you expected"
result := YourMethod(request)
assert.Equal(t, expected, result)
mockReadCloser.AssertExpectations(t)
}
To stop reading you can use:
mockReadCloser.On("Read", mock.AnythingOfType("[]uint8")).Return(0, io.EOF).Once()
As far as I could find perusing the source files for all of the working parts, the only way to get http.Response.Body.Read() to fail is commented here:
https://golang.org/src/net/http/response.go#L53
The response body is streamed on demand as the Body field is read. If
the network connection fails or the server terminates the response,
Body.Read calls return an error.
Or there is the possibility in ioutil.ReadAll() for it to return bytes.ErrTooLarge here:
https://golang.org/src/io/ioutil/ioutil.go#L20
If the buffer overflows, we will get bytes.ErrTooLarge. Return that as
an error. Any other panic remains.

How to write unit tests for function that reads and writes to STDIO in GO? [duplicate]

How do I fill os.Stdin in my test for a function that reads from it using a scanner?
I request a user command line input via a scanner using following function:
func userInput() error {
scanner := bufio.NewScanner(os.Stdin)
println("What is your name?")
scanner.Scan()
username = scanner.Text()
/* ... */
}
Now how do I test this case and simulate a user input?
Following example does not work. Stdin is still empty.
func TestUserInput(t *testing.T) {
var file *os.File
file.Write([]byte("Tom"))
os.Stdin = file
err := userInput()
/* ... */
}
Mocking os.Stdin
You're on the right track that os.Stdin is a variable (of type *os.File) which you can modify, you can assign a new value to it in tests.
Simplest is to create a temporary file with the content you want to simulate as the input on os.Stdin. To create a temp file, use ioutil.TempFile(). Then write the content into it, and seek back to the beginning of the file. Now you can set it as os.Stdin and perform your tests. Don't forget to cleanup the temp file.
I modified your userInput() to this:
func userInput() error {
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("What is your name?")
var username string
if scanner.Scan() {
username = scanner.Text()
}
if err := scanner.Err(); err != nil {
return err
}
fmt.Println("Entered:", username)
return nil
}
And this is how you can test it:
func TestUserInput(t *testing.T) {
content := []byte("Tom")
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up
if _, err := tmpfile.Write(content); err != nil {
log.Fatal(err)
}
if _, err := tmpfile.Seek(0, 0); err != nil {
log.Fatal(err)
}
oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin
os.Stdin = tmpfile
if err := userInput(); err != nil {
t.Errorf("userInput failed: %v", err)
}
if err := tmpfile.Close(); err != nil {
log.Fatal(err)
}
}
Running the test, we see an output:
What is your name?
Entered: Tom
PASS
Also see related question about mocking the file system: Example code for testing the filesystem in Golang
The easy, preferred way
Also note that you can refactor userInput() to not read from os.Stdin, but instead it could receive an io.Reader to read from. This would make it more robust and a lot easier to test.
In your app you can simply pass os.Stdin to it, and in tests you can pass any io.Reader to it created / prepared in the tests, e.g. using strings.NewReader(), bytes.NewBuffer() or bytes.NewBufferString().
os.Pipe()
Instead of messing with the actual file system and doing writes and reads to and from real files on a storage device, the simplest solution is using os.Pipe().
Example
The code of your userInput() does have to be adjusted, and #icza's solution would indeed do for that purpose. But the test itself should be something more like this:
func Test_userInput(t *testing.T) {
input := []byte("Alice")
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
_, err = w.Write(input)
if err != nil {
t.Error(err)
}
w.Close()
// Restore stdin right after the test.
defer func(v *os.File) { os.Stdin = v }(os.Stdin)
os.Stdin = r
if err = userInput(); err != nil {
t.Fatalf("userInput: %v", err)
}
}
Details
There are several important points about this code:
Always close your w stream when you're done writing. Many utilities rely on an io.EOF returned by a Read() call to know that no more data is coming, and the bufio.Scanner is no exception. If you don't close the stream, your scanner.Scan() call will never return, but keep looping internally and waiting for more input until the program is terminated forcefully (as when the test times out).
The pipe buffer capacity varies from system to system, as discussed at length in a post in the Unix & Linux Stack Exchange, so if the size of your simulated input could exceed that, you should wrap your write(s) in a goroutine like so:
//...
go func() {
_, err = w.Write(input)
if err != nil {
t.Error(err)
}
w.Close()
}()
//...
This prevents a deadlock when the pipe is full and writes have to wait for it to start emptying, but the code that's supposed to be reading from and emptying the pipe (userInput() in this case) is not starting, because of writing not being over yet.
A test should also verify that errors are handled properly, in this case, returned by userInput(). This means you'd have to figure out a way to make the scanner.Err() call return an error in a test. One approach could be closing the r stream it was supposed to be reading, before it has had the chance.
Such a test would look almost identical to the nominal case, only you don't write anything at the w end of the pipe, just close the r end, and you actually expect and want userInput() to return an error. And when you have two or more tests of the same function that are almost identical, it is often a good time to implement them as a single table driven test. See Go playground for an example.
io.Reader
The example of userInput() is trivial enough that you could (and should) refactor it and similar cases to read from an io.Reader, just like #icza suggests (see the playground).
You should always strive to rely on some form of dependency injection instead of global state (os.Stdin, in this case, is a global variable in the os package), as that gives more control to the calling code to determine how a called piece of code behaves, which is essential to unit testing, and facilitates better code reuse in general.
Return of os.Pipe()
There may also be cases when you can't really alter a function to take injected dependencies, as when you have to test the main() function of a Go executable. Altering the global state in the test (and hoping that you can properly restore it by the end not to affect subsequent tests) is your only option then. This is where we come back to os.Pipe()
When testing main(), do use os.Pipe() to simulate input to stdin (unless you already hava a file prepared for the purpose) and to capture the output of stdout and stderr (see the playground for an example of the latter).
Implementation of #icza's easy, preferred way:
Also note that you can refactor userInput() to not read from os.Stdin,
but instead it could receive an io.Reader to read from. This would
make it more robust and a lot easier to test.
In your app you can simply pass os.Stdin to it, and in tests you can
pass any io.Reader to it created / prepared in the tests, e.g. using
strings.NewReader(), bytes.NewBuffer() or bytes.NewBufferString().
hello.go
package main
import (
"bufio"
"fmt"
"os"
"io"
)
func userInput(reader io.Reader) error {
scanner := bufio.NewScanner(reader)
var username string
fmt.Println("What is your name?")
if scanner.Scan() {
username = scanner.Text()
}
if scanner.Err() != nil {
return scanner.Err()
}
fmt.Println("Hello", username)
return nil
}
func main() {
userInput(os.Stdin)
}
hello_test.go
package main
import (
"bytes"
"io"
"strings"
"testing"
)
func TestUserInputWithStringsNewReader(t *testing.T) {
input := "Tom"
var reader io.Reader = strings.NewReader(input)
err := userInput(reader)
if err != nil {
t.Errorf("Failed to read from strings.NewReader: %w", err)
}
}
func TestUserInputWithBytesNewBuffer(t *testing.T) {
input := "Tom"
var reader io.Reader = bytes.NewBuffer([]byte(input))
err := userInput(reader)
if err != nil {
t.Errorf("Failed to read from bytes.NewBuffer: %w", err)
}
}
func TestUserInputWithBytesNewBufferString(t *testing.T) {
input := "Tom"
var reader io.Reader = bytes.NewBufferString(input)
err := userInput(reader)
if err != nil {
t.Errorf("Failed to read from bytes.NewBufferString: %w", err)
}
}
Running the program:
go run hello.go
What is your name?
Tom
Hello Tom
Running the test:
go test hello_test.go hello.go -v
=== RUN TestUserInputWithStringsNewReader
What is your name?
Hello Tom
--- PASS: TestUserInputWithStringsNewReader (0.00s)
=== RUN TestUserInputWithBytesNewBuffer
What is your name?
Hello Tom
--- PASS: TestUserInputWithBytesNewBuffer (0.00s)
=== RUN TestUserInputWithBytesNewBufferString
What is your name?
Hello Tom
--- PASS: TestUserInputWithBytesNewBufferString (0.00s)
PASS
ok command-line-arguments 0.141s
You can use *bufio.Scanner to abstract io.Stdin and io.Writer to abstract io.Stdout while passing them as dependencies to your struct, see
Gist: https://gist.github.com/antonzhukov/2a6749f780b24f38b08c9916caa96663 and
Playground: https://play.golang.org/p/BZMqpACupSc

How to write unit tests using httptest with handlers using context in their definitions?

I've been trying for sometime to figure out how a write unit tests for handlers that use context as part of their definition.
Example
func Handler(ctx context.Context, w http.ResponseWriter, r *http.Request)
After some googling I came accross this article which made it seem as simple as
//copied right from the article
rr := httptest.NewRecorder()
// e.g. func GetUsersHandler(ctx context.Context, w http.ResponseWriter, r *http.Request)
handler := http.HandlerFunc(GetUsersHandler)
When trying to to implement the tests like this I was given the error
cannot convert Handler (type func("context".Context, http.ResponseWriter, *http.Request)) to type http.HandlerFunc
So I drove into the definition of HandleFunc and found
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
So the error makes sense... but now I'm at a lose because I need to test this Handler and it doesn't seem like I can using httptest as the article suggested.
Is there anyway to test my handlers with the httptest package? If not how should I go about testing?
I'm using Go1.9
UPDATE
//Just so its clear this is what I'm currently trying to do
data := url.Values{}
data.Set("event_type", "click")
data.Set("id", "1")
data.Set("email", "")
req, err := http.NewRequest("PUT", "/", bytes.NewBufferString(data.Encode()))
Expect(err).To(BeNil())
rr := httptest.NewRecorder()
// this is the problem line.
// The definition of Handler isn't (w ResponseWriter, r *Request)
// So I can't create a handler to serve my mock requests
handler := http.HandlerFunc(Handler)
handler.ServeHTTP(rr, req)
httptest includes everything you need to create a fake Request and ResponseWriter for testing purposes, you just need to create an appropriate fake Context (whatever that means in your situation) and then pass all 3 to your handler function and validate what it writes to the ResponseWriter:
ctx := MakeMyContext()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/myroute", nil)
// headers etc
MyHandler(ctx,w,r)
// Validate status, body, headers, whatever in r

Unit testing in golang

I'm currently looking into creating some unit tests for my service in Go, as well as other functions that build up on top of that functionality, and I'm wondering what is the best way to unit test that in Go? My code looks like:
type BBPeripheral struct {
client *http.Client
endpoint string
}
type BBQuery struct {
Name string `json:"name"`
}
type BBResponse struct {
Brand string `json:"brand"`
Model string `json:"model"`
...
}
type Peripheral struct {
Brand string
Model string
...
}
type Service interface {
Get(name string) (*Peripheral, error)
}
func NewBBPeripheral(config *peripheralConfig) (*BBPeripheral, error) {
transport, err := setTransport(config)
if err != nil {
return nil, err
}
BB := &BBPeripheral{
client: &http.Client{Transport: transport},
endpoint: config.Endpoint[0],
}
return BB, nil
}
func (this *BBPeripheral) Get(name string) (*Peripheral, error) {
data, err := json.Marshal(BBQuery{Name: name})
if err != nil {
return nil, fmt.Errorf("BBPeripheral.Get Marshal: %s", err)
}
resp, err := this.client.Post(this.endpoint, "application/json", bytes.NewBuffer(data))
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(resp.StatusCode)
}
var BBResponse BBResponse
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &BBResponse)
if err != nil {
return nil, err
}
peripheral := &Peripheral{}
peripheral.Model = BBResponse.Model
if peripheral.Model == "" {
peripheral.Model = NA
}
peripheral.Brand = BBResponse.Brand
if peripheral.Brand == "" {
peripheral.Brand = NA
}
return peripheral, nil
}
Is the most efficient way of testing this code and the code that uses these functions to spin up a separate goroutine to act like the server, use http.httptest package, or something else? that's the first time that i try to write a test then i don't realy know how.
It really completely depends. Go provides pretty much all the tools you need to test your application at every single level.
Unit Tests
Design is important because there aren't many tricks to dynamically provide mock/stub objects. You can override variables for tests, but it unlocks all sorts of problems with cleanup. I would focus on IO free unit tests to check that your specific logic works.
For example, you could test BBPeripheral.Get method by making client an interface, requiring it during instantiation, and providing a stub one for the test.
func Test_BBPeripheral_Get_Success(*testing.T) {
bb := BBPeripheral{client: &StubSuccessClient, ...}
p, err := bb.Get(...)
if err != nil {
t.Fail()
}
}
Then you could create a stub error client that exercises error handling in the Get method:
func Test_BBPeripheral_Get_Success(*testing.T) {
bb := BBPeripheral{client: &StubErrClient, ...}
_, err := bb.Get(...)
if err == nil {
t.Fail()
}
}
Component/Integration Tests
These tests can help exercise that each individual unit in your package can work together in unison. Since your code talks over http, Go provides the httptest package that could be used.
To do this the test could create an httptest server with a handler registered to provide the response that this.endpoint expects. You could then exercise your code using its public interface by requesting a NewBBPeripheral, passing in this.endpoint corresponding to the Server.URL property.
This allows you to simulate your code talking to a real server.
Go Routine Tests
Go makes it so easy to write concurrent code, and makes it just as easy to test it. Testing the top level code that spawns a go routine that exercises NewBBPeripheral could look very much like the test above. In addition to starting up a test server your test will have to wait your your asynchronous code to complete. If you don't have a service wide way to cancel/shutdown/signal complete then one may be required to test it using go routines.
RaceCondition/Load Testing
Using go's built in bechmark test combined with -race flag, you can easily exercise your code, and profile it for race conditions, leveraging the tests you wrote above.
One thing to keep in mind, if the implementation of your application is still in flux, writing unit tests may cost a large amount of time. Creating a couple tests, which exercise the public interface of your code, should allow you to easily verify that your application is working, while allowing the implementation to change.

unit testing golang handler

Here's a handler I wrote to retrieve a document from mongodb.
If the document is found, we will load and render the template accordingly.
If it fails, it will redirect to 404.
func EventNextHandler(w http.ResponseWriter, r *http.Request) {
search := bson.M{"data.start_time": bson.M{"$gte": time.Now()}}
sort := "data.start_time"
var result *Event
err := db.Find(&Event{}, search).Sort(sort).One(&result)
if err != nil && err != mgo.ErrNotFound {
panic(err)
}
if err == mgo.ErrNotFound {
fmt.Println("No such object in db. Redirect")
http.Redirect(w, r, "/404/", http.StatusFound)
return
}
// TODO:
// This is the absolute path parsing of template files so tests will pass
// Code can be better organized
var eventnext = template.Must(template.ParseFiles(
path.Join(conf.Config.ProjectRoot, "templates/_base.html"),
path.Join(conf.Config.ProjectRoot, "templates/event_next.html"),
))
type templateData struct {
Context *conf.Context
E *Event
}
data := templateData{conf.DefaultContext(conf.Config), result}
eventnext.Execute(w, data)
}
Manually trying this out, everything works great.
However, I can't seem to get this to pass my unit tests. In a corresponding test file, this is my test code to attempt to load my EventNextHandler.
func TestEventNextHandler(t *testing.T) {
// integration test on http requests to EventNextHandler
request, _ := http.NewRequest("GET", "/events/next/", nil)
response := httptest.NewRecorder()
EventNextHandler(response, request)
if response.Code != http.StatusOK {
t.Fatalf("Non-expected status code%v:\n\tbody: %v", "200", response.Code)
}
}
The test fails at the line stating EventNextHandler(response, request).
And in my error message, it refers to the line err := db.Find(&Event{}, search).Sort(sort).One(&result) in my handler code.
Complete error message here:-
=== RUN TestEventNextHandler
--- FAIL: TestEventNextHandler (0.00 seconds)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x8 pc=0x113eb8]
goroutine 4 [running]:
testing.func·004()
/usr/local/go/src/pkg/testing/testing.go:348 +0xcd
hp/db.Cursor(0xc2000c3cf0, 0xc2000fb1c0, 0x252d00)
/Users/calvin/gopath/src/hp/db/db.go:57 +0x98
hp/db.Find(0xc2000c3cf0, 0xc2000fb1c0, 0x252d00, 0xc2000e55c0, 0x252d00, ...)
/Users/calvin/gopath/src/hp/db/db.go:61 +0x2f
hp/event.EventNextHandler(0xc2000e5580, 0xc2000a16a0, 0xc2000c5680)
/Users/calvin/gopath/src/hp/event/controllers.go:106 +0x1da
hp/event.TestEventNextHandler(0xc2000d5240)
/Users/calvin/gopath/src/hp/event/controllers_test.go:16 +0x107
testing.tRunner(0xc2000d5240, 0x526fe0)
/usr/local/go/src/pkg/testing/testing.go:353 +0x8a
created by testing.RunTests
/usr/local/go/src/pkg/testing/testing.go:433 +0x86b
goroutine 1 [chan receive]:
testing.RunTests(0x3bdff0, 0x526fe0, 0x1, 0x1, 0x1, ...)
/usr/local/go/src/pkg/testing/testing.go:434 +0x88e
testing.Main(0x3bdff0, 0x526fe0, 0x1, 0x1, 0x532580, ...)
/usr/local/go/src/pkg/testing/testing.go:365 +0x8a
main.main()
hp/event/_test/_testmain.go:43 +0x9a
What is the correct way to write my tests? To take into account the situation where nothing gets retrieved from mongodb and apply an assertion to verify that, thus writing a validated test.
It appears that I did not initialize db in my test. Much like a "setup" step similar to all other languages' unit testing framework.
I have to ensure that my db package is imported and then implement two lines to Connect to the database and register the indexes.
func TestEventNextHandler(t *testing.T) {
// set up test database
db.Connect("127.0.0.1", "test_db")
db.RegisterAllIndexes()
// integration test on http requests to EventNextHandler
request, _ := http.NewRequest("GET", "/events/next/", nil)
response := httptest.NewRecorder()
EventNextHandler(response, request)
if response.Code != 302 {
t.Fatalf("Non-expected status code %v:\n\tbody: %v", "200", response.Code)
}
}