I have next struct.
package logger
import "fmt"
type IPrinter interface {
Print(value string)
}
type ConsolePrinter struct{}
func (cp *ConsolePrinter) Print(value string) {
fmt.Printf("this is value: %s", value)
}
Test coverage says I need to test that ConsolePrinter Print method.
How can I cover this method?
Thanks.
Following comment that #icza wrote, I've written test below.
func TestPrint(t *testing.T) {
rescueStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
cp := &ConsolePrinter{}
cp.Print("test")
w.Close()
out, _ := ioutil.ReadAll(r)
os.Stdout = rescueStdout
if string(out) != "this is value: test" {
t.Errorf("Expected %s, got %s", "this is value: test", out)
}
}
I've found example in question What is the best way to convert byte array to string?.
Use Examples to convey the usage of a function.
Don't fret over 100% test coverage, especially for simple straightforward functions.
func ExampleHello() {
fmt.Println("hello")
// Output: hello
}
The additional benefit is that examples are outputted in a generated doc with go doc tool.
I would recommend to create new instance of the logger, which would behave exactly as fmt methods for printing data to console. Also, you can configure it with additional features like showing the filename, date and etc. Such custom logger can be passed as a parameter to your service/instance factory method. That would make it really easy to mock and test.
Your code
type Logs interface {
Println(v ...interface{})
}
type InstanceToTest struct {
log Logs
}
func InstanceToTestFactory(logger Logs) *InstanceToTest {
return &InstanceToTest{logger}
}
func (i *InstanceToTest) SomeMethod(a string) {
i.log.Println(a)
}
Create a mock for logger
type LoggerMock struct {
CalledPrintln []interface{}
}
func (l *LoggerMock) Println(v ...interface{}) {
l.CalledPrintln = append(CalledPrintln, v)
}
And in your test
func TestInstanceToTestSomeMethod(t *testing.T) {
l := &LoggerMock{}
i := InstanceToTestFactory(l)
a := "Test"
i.SomeMethod(a)
if len(l.CalledPrintln) == 0 || l.CalledPrintln[0] != a {
t.Error("Not called")
}
}
Related
How should I unittest following piece of code. I was trying to use coutnerfiter to fake input "*s3.S3" object, but it's not working for me. I am new to coutnerfiter and Go, Can someone please help me on that.
func (l *listContentImp) ListS3Content(client *s3.S3) (bool, error) {
listObj := &s3.ListObjectsV2Input{
Bucket: aws.String(l.bucket),
}
var err error
l.lObj, err = client.ListObjectsV2(listObj)
if err != nil {
return false, err
}
return true, nil
}
You shouldn't pass a reference to the s3.S3 struct. When using the AWS SDK for Go v1 you typically pass the services corresponding interface. For S3 this is s3iface.
The signature of your function would look like this:
func (l *listContentImp) ListS3Content(client s3iface.S3API) (bool, error)
Now every struct that you pass that implements at least one of the methods of s3iface.S3API will work.
At runtime you'll pass the proper service client, but in the unit tests you can just pass a mock:
type mock struct {
s3iface.S3API
output *s3.ListObjectsV2Output
err error
}
func (m mock) ListObjectsV2(*s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, error) {
return m.output, m.err
}
In your test you create the mock and pass it to your function:
func Test_ListObject(t *testing.T) {
l := &listContentImp{...}
m := mock{
output: &s3.ListObjectsV2Output{...},
err: nil
}
result, err := l.ListS3Content(m)
[... add checks here...]
}
Im using the following go-logr/logr library.
I have a test which needs to pass the logger as parameter and check that it was able to log the data that was sent.
I need to test the function GetConfig:
config, err := GetConfig(FilePath, "ns", logger, "test" )
At the end I need to print some message from the logger in the test
Expect(logger.msg).To(Equal("test"))
My question is how should mock it?
I’ve tried with the following but without success
func NewTestLogger() logr.Logger {
l := &testlogger{
Formatter: funcr.NewFormatter(funcr.Options{}),
}
return logr.New(l)
}
var _ = Describe(“test” action, func() {
It("action configuration with logger", func() {
//var t *testing.T
tl := NewTestLogger()
config, err := GetConfig(kcfgFilePath, "ns", tl, "test")
But Im not able to print the value from the logger, how can I do it right?
Something like
assert.Contains(t, tl.sink.Output, "test")
Should I use the testing package?
update
This is a working version without the assertion.
Not sure what I miss there as I want to assert the data that are coming from the output of the GetConfig 'tl` and get the key and value
This is close to the code I've in prod, how can I make work?
https://go.dev/play/p/XDDkNjkESUw
My Question is how should I assert the following
assert.Contains(t, tl.GetSink().WithName("Output"), "test")
assert.Contains(t, tl.GetSink().WithName("Output"), "message")
assert.Contains(t, tl.GetSink().WithName("Output"), "print something")
I was able to get the data like following, but not sure how to assert the values
The logr.New function accepts any implementation of the LogSink interface - This means you should just implement one that saves the calls onto a slice in-memory instead of printing, and then you can expect that the slice has your log output.
package main
import (
"testing"
"github.com/stretchr/testify/assert"
// ... some extra imports
)
type TestLogger struct {
Output map[string]map[int][]interface{}
r RuntimeInfo
}
func (t *TestLogger) doLog(level int, msg string, keysAndValues ...interface{}) {
m := make(map[int][]interface{}, len(keysAndValues))
m[level] = keysAndValues
t.output[msg] = m
}
func (t *TestLogger) Init(info RuntimeInfo) { t.r = info}
func (t *TestLogger) Enabled(level int) bool {return true}
func (t *TestLogger) Info(level int, msg string, keysAndValues ...interface{}) { t.doLog(level, msg, keysAndValues...) }
func (t *TestLogger) Error(err error, msg string, keysAndValues ...interface{}) { t.doLog(level, msg, append(keysAndValues, err)...) }
func (t *TestLogger) WithValues(keysAndValues ...interface{}) LogSink { return t}
func (t *TestLogger) WithName(name string) LogSink { return t }
func TestLoggerHasOutput(t *testing.T) {
l := &TestLogger{make(map[string]map[int][]interface[]), RuntimeInfo{1}}
tl := logr.New(l)
config, err := GetConfig(kcfgFilePath, "ns", tl, "test")
assert.Contains(t, l.Output, "ns") // you can also test the contents of the output as well
}
I am writing unit test in golang by https://github.com/stretchr/testify
Suppose I have a method below,
func DoSomething(result interface{}) error {
// write some data to result
return nil
}
so the caller can call DoSomething as following
result := &SomeStruct{}
err := DoSomething(result)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("The result is", result)
}
Now I know how to use testify or some other mocking tools to mock the returns value (it's err here) by something like
mockObj.On("DoSomething", mock.Anything).Return(errors.New("mock error"))
My question is "how do i mock the result argument" in this kind of scenario?
Since result is not a return value but a argument, the caller calls it by passing a pointer of a struct, and the function modify it.
You can use the (*Call).Run method:
Run sets a handler to be called before returning. It can be used when
mocking a method (such as an unmarshaler) that takes a pointer to a
struct and sets properties in such struct
Example:
mockObj.On("Unmarshal", mock.AnythingOfType("*map[string]interface{}")).Return().Run(func(args Arguments) {
arg := args.Get(0).(*map[string]interface{})
arg["foo"] = "bar"
})
As #bikbah said, here is an example:
services/message.go:
type messageService struct {
HttpClient http.Client
BaseURL string
}
func (m *messageService) MarkAllMessages(accesstoken string) []*model.MarkedMessage {
endpoint := m.BaseURL + "/message/mark_all"
var res model.MarkAllMessagesResponse
if err := m.HttpClient.Post(endpoint, &MarkAllMessagesRequestPayload{Accesstoken: accesstoken}, &res); err != nil {
fmt.Println(err)
return res.MarkedMsgs
}
return res.MarkedMsgs
}
We passes res to the m.HttpClient.Post method. In this method, the res will be populated with json.unmarshal method.
mocks/http.go:
package mocks
import (
"io"
"github.com/stretchr/testify/mock"
)
type MockedHttp struct {
mock.Mock
}
func (m *MockedHttp) Get(url string, data interface{}) error {
args := m.Called(url, data)
return args.Error(0)
}
func (m *MockedHttp) Post(url string, body interface{}, data interface{}) error {
args := m.Called(url, body, data)
return args.Error(0)
}
services/message_test.go:
package services_test
import (
"errors"
"reflect"
"strconv"
"testing"
"github.com/stretchr/testify/mock"
"github.com/mrdulin/gqlgen-cnode/graph/model"
"github.com/mrdulin/gqlgen-cnode/services"
"github.com/mrdulin/gqlgen-cnode/mocks"
)
const (
baseURL string = "http://localhost/api/v1"
accesstoken string = "123"
)
func TestMessageService_MarkAllMessages(t *testing.T) {
t.Run("should mark all messaages", func(t *testing.T) {
testHttp := new(mocks.MockedHttp)
var res model.MarkAllMessagesResponse
var markedMsgs []*model.MarkedMessage
for i := 1; i <= 3; i++ {
markedMsgs = append(markedMsgs, &model.MarkedMessage{ID: strconv.Itoa(i)})
}
postBody := services.MarkAllMessagesRequestPayload{Accesstoken: accesstoken}
testHttp.On("Post", baseURL+"/message/mark_all", &postBody, &res).Return(nil).Run(func(args mock.Arguments) {
arg := args.Get(2).(*model.MarkAllMessagesResponse)
arg.MarkedMsgs = markedMsgs
})
service := services.NewMessageService(testHttp, baseURL)
got := service.MarkAllMessages(accesstoken)
want := markedMsgs
testHttp.AssertExpectations(t)
if !reflect.DeepEqual(got, want) {
t.Errorf("got wrong return value. got: %#v, want: %#v", got, want)
}
})
t.Run("should print error and return empty slice", func(t *testing.T) {
var res model.MarkAllMessagesResponse
testHttp := new(mocks.MockedHttp)
postBody := services.MarkAllMessagesRequestPayload{Accesstoken: accesstoken}
testHttp.On("Post", baseURL+"/message/mark_all", &postBody, &res).Return(errors.New("network"))
service := services.NewMessageService(testHttp, baseURL)
got := service.MarkAllMessages(accesstoken)
var want []*model.MarkedMessage
testHttp.AssertExpectations(t)
if !reflect.DeepEqual(got, want) {
t.Errorf("got wrong return value. got: %#v, want: %#v", got, want)
}
})
}
In the unit test case, we populated the res in #Call.Run method and assigned the return value(res.MarkedMsgs) of service.MarkAllMessages(accesstoken) to got variable.
unit test result and coverage:
=== RUN TestMessageService_MarkAllMessages
--- PASS: TestMessageService_MarkAllMessages (0.00s)
=== RUN TestMessageService_MarkAllMessages/should_mark_all_messaages
TestMessageService_MarkAllMessages/should_mark_all_messaages: message_test.go:39: PASS: Post(string,*services.MarkAllMessagesRequestPayload,*model.MarkAllMessagesResponse)
--- PASS: TestMessageService_MarkAllMessages/should_mark_all_messaages (0.00s)
=== RUN TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice
network
TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice: message_test.go:53: PASS: Post(string,*services.MarkAllMessagesRequestPayload,*model.MarkAllMessagesResponse)
--- PASS: TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice (0.00s)
PASS
coverage: 5.6% of statements in ../../gqlgen-cnode/...
Process finished with exit code 0
I highly recommend to get familiar with the gomock framework and develop towards interfaces. What you need would look something like this.
// SetArg does the job
myObj.EXPECT().DoSomething(gomock.Any()).SetArg(0, <value you want to r eturn>).Return(nil)
https://github.com/golang/mock#building-mocks
I'm writing a library of testing utility functions and I would like them to be themselves tested.
An example of one such function is:
func IsShwifty(t *testing.T, foo string) bool {
result, err := bar.MyFunction(string)
if err != nil {
t.Error("Failed to get shwifty: " + foo, err)
}
return result == "shwifty"
}
I would like to write a TestIsShwifty that feeds in something to make MyFunction return an error and then make t.Error. Then, I want to have the TestIsShwifty pass with something like:
func TestIsShwifty(t *testing.T) {
if doesError(t, IsShwifty(t)) == false {
t.Error("Oh know! We didn't error!")
}
}
Is this possible in Go?
I figured it out!
I just needed to create a separate instance of testing.T.
func TestIsShwifty(t *testing.T) {
newT := testing.T{}
IsShwifty(newT)
if newT.Failed() == false {
t.Error("Test should have failed")
}
}
I'm writing a REST API using Gin framework. But I was faced a trouble testing my controllers and researching TDD and Mock. I tried to apply TDD and Mock to my code but I could not.
I created a very reduced test environment and tried to create a controller test. How do I create a Mock for Gin.Context?
Here's my example code:
package main
import (
"strconv"
"github.com/gin-gonic/gin"
)
// MODELS
type Users []User
type User struct {
Name string `json"name"`
}
func main() {
r := gin.Default()
r.GET("/users", GetUsers)
r.GET("/users/:id", GetUser)
r.Run(":8080")
}
// ROUTES
func GetUsers(c *gin.Context) {
repo := UserRepository{}
ctrl := UserController{}
ctrl.GetAll(c, repo)
}
func GetUser(c *gin.Context) {
repo := UserRepository{}
ctrl := UserController{}
ctrl.Get(c, repo)
}
// CONTROLLER
type UserController struct{}
func (ctrl UserController) GetAll(c *gin.Context, repository UserRepositoryIterface) {
c.JSON(200, repository.GetAll())
}
func (ctrl UserController) Get(c *gin.Context, repository UserRepositoryIterface) {
id := c.Param("id")
idConv, _ := strconv.Atoi(id)
c.JSON(200, repository.Get(idConv))
}
// REPOSITORY
type UserRepository struct{}
type UserRepositoryIterface interface {
GetAll() Users
Get(id int) User
}
func (r UserRepository) GetAll() Users {
users := Users{
{Name : "Wilson"},
{Name : "Panda"},
}
return users
}
func (r UserRepository) Get(id int) User {
users := Users{
{Name : "Wilson"},
{Name : "Panda"},
}
return users[id-1]
}
My test example:
package main
import(
"testing"
_ "github.com/gin-gonic/gin"
)
type UserRepositoryMock struct{}
func (r UserRepositoryMock) GetAll() Users {
users := Users{
{Name : "Wilson"},
{Name : "Panda"},
}
return users
}
func (r UserRepositoryMock) Get(id int) User {
users := Users{
{Name : "Wilson"},
{Name : "Panda"},
}
return users[id-1]
}
// TESTING REPOSITORY FUNCTIONS
func TestRepoGetAll(t *testing.T) {
userRepo := UserRepository{}
amountUsers := len(userRepo.GetAll())
if amountUsers != 2 {
t.Errorf("Esperado %d, recebido %d", 2, amountUsers)
}
}
func TestRepoGet(t *testing.T) {
expectedUser := struct{
Name string
}{
"Wilson",
}
userRepo := UserRepository{}
user := userRepo.Get(1)
if user.Name != expectedUser.Name {
t.Errorf("Esperado %s, recebido %s", expectedUser.Name, user.Name)
}
}
/* HOW TO TEST CONTROLLER?
func TestControllerGetAll(t *testing.T) {
gin.SetMode(gin.TestMode)
c := &gin.Context{}
c.Status(200)
repo := UserRepositoryMock{}
ctrl := UserController{}
ctrl.GetAll(c, repo)
}
*/
Gin provides the option to create a Test Context which you can use for whatever you need:
https://godoc.org/github.com/gin-gonic/gin#CreateTestContext
Like that:
c, _ := gin.CreateTestContext(httptest.NewRecorder())
Here is an example of how I mock a context, add a param, use it in a function, then print the string of the response if there was a non-200 response.
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = []gin.Param{gin.Param{Key: "k", Value: "v"}}
foo(c)
if w.Code != 200 {
b, _ := ioutil.ReadAll(w.Body)
t.Error(w.Code, string(b))
}
In order to get a *gin.Context instance that you can test, you need a mock HTTP request and response. An easy way to create those is to use the net/http and net/http/httptest packages. Based on the code you linked, your test would look like this:
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestControllerGetAll(t *testing.T) {
// Switch to test mode so you don't get such noisy output
gin.SetMode(gin.TestMode)
// Setup your router, just like you did in your main function, and
// register your routes
r := gin.Default()
r.GET("/users", GetUsers)
// Create the mock request you'd like to test. Make sure the second argument
// here is the same as one of the routes you defined in the router setup
// block!
req, err := http.NewRequest(http.MethodGet, "/users", nil)
if err != nil {
t.Fatalf("Couldn't create request: %v\n", err)
}
// Create a response recorder so you can inspect the response
w := httptest.NewRecorder()
// Perform the request
r.ServeHTTP(w, req)
// Check to see if the response was what you expected
if w.Code != http.StatusOK {
t.Fatalf("Expected to get status %d but instead got %d\n", http.StatusOK, w.Code)
}
}
Although you could create a mock *gin.Context, it's probably easier to use the method above, since it'll execute and handle your request the same as it would an actual request.
If to reduce the question to "How to create mock for a function argument?" the answer is: use interfaces not concrete types.
type Context struct is a concrete type literal and Gin doesn't provide appropriate interface. But you can declare it by yourself. Since you are using only JSON method from Context you can declare extra-simple interface:
type JSONer interface {
JSON(code int, obj interface{})
}
And use JSONer type instead Context type in all your functions which expect Context as argument:
/* Note, you can't declare argument as a pointer to interface type,
but when you call it you can pass pointer to type which
implements the interface.*/
func GetUsers(c JSONer) {
repo := UserRepository{}
ctrl := UserController{}
ctrl.GetAll(c, repo)
}
func GetUser(c JSONer) {
repo := UserRepository{}
ctrl := UserController{}
ctrl.Get(c, repo)
}
func (ctrl UserController) GetAll(c JSONer, repository UserRepositoryIterface) {
c.JSON(200, repository.GetAll())
}
func (ctrl UserController) Get(c JSONer, repository UserRepositoryIterface) {
id := c.Param("id")
idConv, _ := strconv.Atoi(id)
c.JSON(200, repository.Get(idConv))
}
And now it is easy to test
type ContextMock struct {
JSONCalled bool
}
func (c *ContextMock) JSON(code int, obj interface{}){
c.JSONCalled = true
}
func TestControllerGetAll(t *testing.T) {
gin.SetMode(gin.TestMode)
c := &ContextMock{false}
c.Status(200)
repo := UserRepositoryMock{}
ctrl := UserController{}
ctrl.GetAll(c, repo)
if c.JSONCalled == false {
t.Fail()
}
}
Example simple as possible.
There is another question with a close sense