How to test the ioutil.ReadFile and os.Stat? - unit-testing

I have the following function:
func GetDataFromFile(path string) ([]byte, error) {
_, err := os.Stat(path)
if err != nil {
return nil, err
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return data, nil
}
I want to do tests for functions ioutil.ReadFile and os.Stat(path) when they throw errors.
I know that I can create non-exist path for os.Stat(path), but how to test such kind functions without "workarounds" and guessing how functions are working?
Regards.

I agree with abhink here, I would not expect you to test this particular function. But in practice, similar situation happens often.
My best solution is to use a factory to create GetDataFromFile. In this case, you inject the dependencies.
main.go
package main
import (
"io/ioutil"
"os"
)
func getDataFromFileFactory(
stat func(filename string) (os.FileInfo, error),
readFile func(filename string) ([]byte, error),
) func(path string) ([]byte, error) {
return func(path string) ([]byte, error) {
_, err := stat(path)
if err != nil {
return nil, err
}
data, err := readFile(path)
if err != nil {
return nil, err
}
return data, nil
}
}
var GetDataFromFile = getDataFromFileFactory(os.Stat, ioutil.ReadFile)
func main() {}
main_test.go
package main
import (
"errors"
"os"
"testing"
)
func TestGetDataFromFile(t *testing.T) {
stat := func(filename string) (os.FileInfo, error) {
return nil, errors.New("err msg")
}
readfile := func(filename string) ([]byte, error) {
t.Error("should not call this function")
return nil, nil
}
getDataFromFile := getDataFromFileFactory(stat, readfile)
if _, err := getDataFromFile("foo"); err.Error() != "err msg" {
t.Error("expected an error to be thrown")
}
}

Is there any specific reason you want to test these library functions?
You should only concern yourself with testing your own code and logic. Libraries are tested by those who create and maintain them and you should use them fully expecting to perform as per their documentation. Any genuine deviation from expected behavior should be reported to the authors/maintainers of the library.
As for testing GetDataFromFile, it would be perfectly legitimate to test it by supplying incorrect path. For more specific errors you can also read up on the input parameters that would cause them and call the functions with those arguments.

Related

Unit-testing grpc functions in golang

I have created a function that utilizes the grpc package in golang. I don't know if it is relevant but the purpose is the communication with a GoBGP router over grpc. An example is the following function which prints all the peers (neighbors) of the router:
func (gc *Grpc) Peers(conn *grpc.ClientConn) error {
defer conn.Close()
c := pb.NewGobgpApiClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
p := pb.ListPeerRequest{}
peer, err := c.ListPeer(ctx, &p)
if err != nil {
return err
}
for {
res, err := peer.Recv()
if err != nil {
return err
}
fmt.Println(res)
}
return nil
}
Now, I want to create unit tests for the function. To do so, I used google.golang.org/grpc/test/bufconn package, and initialized the following:
type server struct {
pb.UnimplementedGobgpApiServer
}
func (s *server) ListDefinedSet(in *pb.ListDefinedSetRequest, ls pb.GobgpApi_ListDefinedSetServer) error {
return nil
}
var lis *bufconn.Listener
const bufSize = 1024 * 1024
func init() {
lis = bufconn.Listen(bufSize)
s := grpc.NewServer()
pb.RegisterGobgpApiServer(s, &server{})
go func() {
if err := s.Serve(lis); err != nil {
fmt.Println("Server failed!")
}
}()
}
func bufDialer(context.Context, string) (net.Conn, error) {
return lis.Dial()
}
This way, I can run a unit-test creating a connection as follows:
ctx := context.Background()
conn, _ := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
Peers(conn)
However, the problem is that the stream seems to be always empty and thus the peer.Recv()
always returns EOF. Is there any way to populate the stream with dummy data? If you have experience, is my methodology correct?

Trigger http client error for testing with httptest

I have a simple function which takes a URL and fetches the response:
func getUrl(url string) (string, error) {
var theClient = &http.Client{Timeout: 12 * time.Second}
resp, err := theClient.Get(url)
if err != nil {
return "", err
}
defer r.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return "", readErr
}
return string(body), nil
}
Now, I want to trigger an error on the theClient.Get(url) line but I don't know how to. I can trigger an error on the ReadAll() line, by returning no response but with content-length:2.
How can I trigger an error on the theClient.Get(url) line for my unit test?
func TestGetUrl(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "2")
}))
defer server.Close()
gotContent, gotErr := getUrl(server.URL)
wantErr := "unexpected EOF"
if gotErr == nil || gotErr.Error() != wantErr {
t.Errorf("got err %v; wanted %s", gotErr, wantErr)
}
}
Easiest way is to simply pass an invalid URL:
_, err := http.Get("clearly not a valid url")
fmt.Println("Got error:", err != nil) // Got error: true
Another option is to make it timeout by sleeping in your httptest.Server handler, but that doesn't seem like a very nice idea (but you will be able to assert that it was called in the first place).

How do I unit test this promptui package written in golang?

I am new to golang and I am using an interactive prompt called promptui (https://github.com/manifoldco/promptui) in a project of mine. I have written several unit tests for this project already but I am struggling with how I would unit test this particular package that requires an input.
For example, How would I go about testing the following lines of code (encapsulated in a function):
func setEmail() string {
prompt := promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
email, err := prompt.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}
I think I need to somehow mock stdin but can't figure out the best way to do that within a test.
You should not try to test promptui as it is expected to be tested by its author.
What you can test:
You send correct parameters when you create promptui.Prompt
You use that promptui.Prompt in your code
You properly handle promptui.Prompt results
As you can see, all these tests does not verify if promptui.Prompt works correctly inside.
Tests #2 and #3 could be combined. You need to run you code against mock and if you got correct result, you can believe that both #2 and #3 are correct.
Create mock:
type Runner interface {
Run() (int, string, error)
}
type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}
func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}
You will need separate mock for testing error flow.
Update your code to inject mock:
func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}
Now it is testable.
Create function that creates prompt:
func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}
Write simple assert test to verify that we create correct structure.
The only not tested line will be setEmail(getRunner()) but it is trivial and can be covered by other types of tests.
For whatever reason, they don't export their stdin interface (https://github.com/manifoldco/promptui/blob/master/prompt.go#L49), so you can't mock it out, but you can directly mock os.Stdin and prefill it with whatever you need for testing. Though I agree with #Adrian, it has its own tests, so this shouldn't be necessary.
Extracted and refactored/simplified from source: Fill os.Stdin for function that reads from it
Refactored this way, it can be used for any function that reads from os.Stdin and expects a specific string.
Playground link: https://play.golang.org/p/rjgcGIaftBK
func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email#test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}
func TestExpectedStdinFunc(expected string, f func() string) error {
content := []byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}
defer os.Remove(tmpfile.Name()) // clean up
if _, err := tmpfile.Write(content); err != nil {
return err
}
if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}
oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin
os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}
if err := tmpfile.Close(); err != nil {
return err
}
return nil
}
promptui now has the Stdin property.
There is a fiddle here: https://play.golang.org/p/-mSgjY2kAw-
Here is our function that we will be testing:
func mock(p promptui.Prompt) string {
p.Label = "[Y/N]"
user_input, err := p.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
}
return user_input
}
We need to create p, which will be an instance of promptui.Prompt and have a custom Stdin.
I got some help here - https://groups.google.com/g/golang-nuts/c/J-Y4LtdGNSw?pli=1 - in how to make a custom Stdin value, which simply has to conform to io.ReadCloser.
type ClosingBuffer struct {
*bytes.Buffer
}
func (cb ClosingBuffer) Close() error {
return nil
}
And then you use that as Stdin in the reader:
func TestMock(t *testing.T) {
reader := ClosingBuffer{
bytes.NewBufferString("N\n"),
}
p := promptui.Prompt{
Stdin: reader,
}
response := mock(p)
if !strings.EqualFold(response, "N") {
t.Errorf("nope!")
}
//t.Errorf(response)
}
edit: The above doesn't work for multiple prompts within the same function, as discussed here with a solution: https://github.com/manifoldco/promptui/issues/63 - "promptui internally uses a buffer of 4096 bytes. This means that you must pad your buffer or promptui will raise EOF."
I took this pad() function from that exchange - https://github.com/sandokandias/capiroto/blob/master/cmd/capiroto/main.go:
func pad(siz int, buf *bytes.Buffer) {
pu := make([]byte, 4096-siz)
for i := 0; i < 4096-siz; i++ {
pu[i] = 97
}
buf.Write(pu)
}
Then the test - - this solution uses ioutil.NopCloser rather than creating a new struct:
func TestMock(t *testing.T) {
i1 := "N\n"
i2 := "Y\n"
b := bytes.NewBuffer([]byte(i1))
pad(len(i1), b)
reader := ioutil.NopCloser(
b,
)
b.WriteString(i2)
pad(len(i2), b)
p := promptui.Prompt{
Stdin: reader,
}
response := mock(p)
if !strings.EqualFold(response, "NY") {
t.Errorf("nope!")
t.Errorf(response)
}
}
and the function we are testing:
func mock(p promptui.Prompt) string {
p.Label = "[Y/N]"
user_input, err := p.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
}
user_input2, err := p.Run()
return user_input + user_input2
}
The fiddle for multiple prompts is here: https://play.golang.org/p/ElPysYq8aM1

Mock external dependencies in golang

I have a program in go that connects to AWS S3 and gets a file.
I'd like to write some tests for it, but I'd like to know, more generally, how to do these mocks in Golang. I know there are some libraries to create mocks but if I remember correctly I read someone suggesting using only standard libraries for unit tests was the best way to go.
So, how would you test a function like this?
func (s S3Input) Sample(key string) ([]byte, error) {
var buf []byte
waBuf := aws.NewWriteAtBuffer(buf)
_, err := s.Downloader.Download(
waBuf,
&s3.GetObjectInput{
Bucket: aws.String(s.Bucket),
Key: aws.String(key),
},
)
if err != nil {
return nil, err
}
return buf, nil
}
Thank you!
One way to do it is to inject the dependencies in your structure, like such:
type S3Inputer interface {
NewWriteAtBuffer(buf []byte) *aws.WriteAtBuffer
String(v string) *string
}
type S3Input struct {
newWriteAtBufferFunc func(buf []byte) *aws.WriteAtBuffer
stringFunc func(v string) *string
}
func (s *S3Input) NewWriteAtBuffer(buf []byte) *WriteAtBuffer {
return s.newWriteAtBufferFunc(buf)
}
func (s *S3Input) String(v string) *string {
return s.stringFunc(v)
}
func (s S3Input) Sample(key string) ([]byte, error) {
var buf []byte
waBuf := s.NewWriteAtBuffer(buf)
_, err := s.Downloader.Download(
waBuf,
&s3.GetObjectInput{
Bucket: s.String(s.Bucket),
Key: s.String(key),
},
)
if err != nil {
return nil, err
}
return buf, nil
}
func main() {
s := &S3Input{
StringFunc: aws.String,
NewWriteAtBufferFunc: aws.NewWriteAtBuffer,
}
// ...
}
This allows you to replace those functions with whatever you want for testing, without the need of any testing framework.
Then, the testing function would look something like this:
func (s S3Input) TestSample(t *testing.T) {
s3Mock := &S3Input{
StringFunc: (func (v string) *string {
return nil
}),
NewWriteAtBufferFunc: (func (buf []byte) *aws.WriteAtBuffer {
return nil
}),
}
res, err := s3Mock.Sample(...) //
// asserts & error checks
}
You could improve it by creating a S3InputMock type instead of reusing the base one, both would implement the S3Inputer interface and your mock could have attributes allowing it to help you with testing. For example, it could count the number of times a function is called, store the arguments it received, have its methods behave differently depending on the attributes you set for easier testing, etc.

Inconsistent Go error return using if-construction with cypher/aes

I'm seeing strange behaviour where I should get an error back from a function, but I get nil instead.
The following code block contains 2 encrypt functions using cypher/aes. The only difference is the first 1/2 lines of each function. In encrypt2, I've combined the assignment of the first line of encrypt1 into the conditional.
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
func main() {
invalidKey := []byte("TCbKgXZsT")
plaintext := []byte("dummycontenttoenctrypt")
fmt.Println(encrypt1(plaintext, invalidKey))
fmt.Println(encrypt2(plaintext, invalidKey))
}
func encrypt1(plaintext []byte, key []byte) (encrypted []byte, err error) {
c, err := aes.NewCipher(key)
if err == nil {
if gcm, err := cipher.NewGCM(c); err == nil {
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err == nil {
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
}
}
return nil, err
}
func encrypt2(plaintext []byte, key []byte) (encrypted []byte, err error) {
if c, err := aes.NewCipher(key); err == nil {
if gcm, err := cipher.NewGCM(c); err == nil {
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err == nil {
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
}
}
return nil, err
}
Go Playground Link
I expected the same behaviour from these functions, as the logic is the same. However, calling encrypt1 returns an error (correct), while encrypt2 does not return the error (returns just nil).
I have used named arguments, so err is declared at the start of the function and should be populated by the first error in both functions.
Anything I'm doing wrong here?
This is to do with scoping rules, here is a simplified example:
https://play.golang.org/p/1dCaUB948p
func encrypt2(plaintext []byte, key []byte) (encrypted []byte, err error) {
if _, err := returnErr(); err == nil {
fmt.Println("inner loop")
}
return nil, err
}
If you remove the named arguments, it won't compile, which gives a hint of the problem - the err declared in your if statement does not affect the outer scope, it is only valid within the if statement. If you remove the := assignment, you'll see what you expect.
In go a more verbose version which returns on error is usually preferred, rather than lots of nested if statements, so that you can return specific errors and the flow of logic is clear. It's easy if nesting to forget scope or which variables are assigned when, whereas if you adopt this return on error style the reader knows at any given point no errors have been encountered. You don't have to annotate errors where the original error is self-evident of course, you could just return nil,err.
func encrypt3(plaintext []byte, key []byte) ([]byte, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("encrypt: invalid key :%s", err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, fmt.Errorf("encrypt: error creating cipher :%s", err)
}
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return nil, fmt.Errorf("encrypt: error creating nonce :%s", err)
}
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
If you're doing aes encryption in Go, a good ref is this code and video by George Tankersley.