Golang how to test function that return channel type? - unit-testing

I try to test function StartP,
Expect that Start() should be called 1 times, Done() should be called 1 times
but I have trouble that test will block when run this step <-ps.Done()
I expect <-ps.Done() return nil
How can I test function that return chan type?
// production code
func (s *vService) StartP(ctx context.Context, reason string) error {
ps, err := s.factory.CreateVService(ctx)
if err != nil {
return err
}
ps.Start(reason)
err = <-ps.Done() // code stop here to wait ? how can i test ?
if err != nil {
return err
}
return nil
}
// test code
func Test_StartP(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockPService := mockpservice.NewMockPInterface(mockCtrl)
vService := &vService {
factory: &servicefactory.FakeServiceFactory{
MockPService: mockPService
}
}
mockPService.EXPECT().Start("reason").Times(1).Return()
mockPService.EXPECT().Done().Times(1).DoAndReturn(func() chan error {
return nil
})
err := vService.StartP(context.Background(), "reason")
assert.Equal(t, nil, err)
}
I use gomock to mock the PServiceInterface
// interface
type PServiceInterface interface {
Start(reason string)
Done() <-chan error
}
gomock gen this function
func (m *MockProvisionServiceInterface) Done() <-chan error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Done")
ret0, _ := ret[0].(<-chan error)
fmt.Println(ret0,".....mock Done()")
return ret0
}
// I also try this
mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() chan error {
fmt.Println("DoAndReturn...err nil")
ch := make(chan error, 1)
ch <- nil
return ch
})

The following shows, I think, the minimum code to implement your test goals.
It does not use any mocking framework because in my experience they tend to obfuscate the test intent, require everybody in the team to learn how to use them and are not needed, at least in Go. One could also wonder what the test is actually testing...
First, let's add some missing production code:
type factoryInterface interface {
CreateVService(ctx context.Context) (PServiceInterface, error)
}
type vService struct {
factory factoryInterface
}
And now the test code, in three parts: the factory, the mock, and the test.
The test factory:
type testFactory struct {
mock PServiceInterface
}
func (f *testFactory) CreateVService(ctx context.Context) (PServiceInterface, error) {
return f.mock, nil
}
The mock:
type ServiceMock struct {
records []string
}
func (sm *ServiceMock) Start(reason string) {
sm.records = append(sm.records, "start")
}
func (sm *ServiceMock) Done() <-chan error {
sm.records = append(sm.records, "done")
ch := make(chan error)
close(ch)
return ch
}
And finally the test:
func TestWithMock(t *testing.T) {
mock := ServiceMock{}
sut := &vService{factory: &testFactory{&mock}}
err := sut.StartP(context.Background(), "banana")
if err != nil {
t.Fatalf("StartP: have: %s; want: no error", err)
}
if have, want := len(mock.records), 2; have != want {
t.Fatalf("number of mock calls: have: %v; want: %v", have, want)
}
if have, want := mock.records[0], "start"; have != want {
t.Fatalf("mock call 1: have: %v; want: %v", have, want)
}
if have, want := mock.records[1], "done"; have != want {
t.Fatalf("mock call 2: have: %v; want: %v", have, want)
}
}
The three assertions on the mock call sequence can be collapsed into one, comparing directly the slice []string{"start", "done"}, if one is using a test library such as the excellent assert package of https://github.com/gotestyourself/gotest.tools

I found the answer, root cause is DoAndReturn something wrong.
Func type should be <-chan error, not chan error
mockProvisionService.EXPECT().Done().Times(1).DoAndReturn( func() <-chan error{
fmt.Println("DoAndReturn...err nil")
ch := make(chan error, 1)
ch <- nil
return ch
})

Related

How do I mock netconf session in unit tests Golang

I am using juniper's netconf package ("github.com/Juniper/go-netconf/netconf") to establish a netconf session in my code.
I wanted to know how can I mock a netconf session in my unit tests.
My methods are:
func TestMyFunction(t *testing.T) {
getSSHConnection = mockGetSSHConnection
got := MyFunction()
want := 123
if !reflect.DeepEqual(got, want) {
t.Errorf("Error expectation not met, want %v, got %v", want, got)
}
}
func mockGetSSHConnection() (*netconf.Session, error) {
var sess netconf.Session
sess.SessionID = 123
return &sess, nil
}
The problem arises when MyFunction() has a line that defers sess.Close() and it's throwing error due to nil pointer dereference
func MyFunction() int {
sess, err := getSSHConnection() // returns (*netconf.Session, error)
if err == nil && sess != nil {
defer sess.Close() -> Problem happens here
// Calls RPC here and rest of the code here
}
return 0
}
So, what changes can I make on mockGetSSHConnection() method so that sess.Close() won't throw error?
The nil pointer error originates within the Close function when Close is called on the underlying Transport. Fortunately Transport is an interface type that you can easily mock and use in an actual instance of the netconf.Session. For example like so:
type MockTransport struct{}
func (t *MockTransport) Send([]byte) error {
return nil
}
func (t *MockTransport) Receive() ([]byte, error) {
return []byte{}, nil
}
func (t *MockTransport) Close() error {
return nil
}
func (t *MockTransport) ReceiveHello() (*netconf.HelloMessage, error) {
return &netconf.HelloMessage{SessionID: 123}, nil
}
func (t *MockTransport) SendHello(*netconf.HelloMessage) error {
return nil
}
func (t *MockTransport) SetVersion(version string) {
}
func mockGetSSHConnection() (*netconf.Session, error) {
t := MockTransport{}
sess := netconf.NewSession(&t)
return sess, nil
}
Note that the function you want to test currently return 0 and not the SessionID of the session. So you should fix that before the test is successful.
You could use OOP and "github.com/stretchr/testify/mock" package
for example create
type SshClientMock struct {
mock.Mock
}
func (s *SshClientMock) GetSSHConnection() {
return //what do you need
}
in your unit test:
sshClient := SshClientMock
sshClient.On("GetSSHConnection").Return(what do you need)
and then call your method

Mock asserting into callbacks

I'm having the following problem when unit testing (working code here]
Assuming we have this:
var (
userDomain UserInterface
Tx TxInterface
)
type UserInterface interface {
Get() (*User, error)
}
type TxInterface interface {
Exec(fn func() error) error
}
type User struct {
ID int64
}
func (u *User) Get() (*User, error) {
return &User{
ID: 1,
}, nil
}
type WithTx struct{}
func (t *WithTx) Exec(fn func() error) error {
/* more logic here */
return fn()
}
Assuming we have the following 2 services:
func GetByID() (*User, error) {
user, err := userDomain.Get()
return user, err
}
func GetByIDWithTx() (*User, error) {
u := &User{}
/** Notice this implementation **/
if err := Tx.Exec(func() error {
user, _ := userDomain.Get()
u = user
return nil
}); err != nil {
log.Print("Tx err", err)
}
return u, nil
}
They are the same but once goes directly to the Get method, the other one runs into a callback fn. So far so go, both works like a charm. Again code is here
I'm trying to mock the userDomain and Tx for unit tests, so my mocks look like this:
var (
getUserFromMock func() (*User, error)
getFromMock func() error
)
type userMock struct{}
func (u *userMock) Get() (*User, error) {
return getUserFromMock()
}
type txMock struct{}
func (t *txMock) Exec(fn func() error) error {
return getFromMock()
}
When testing GetByID I have no problem whatsoever.
func TestGetByID(t *testing.T) {
userDomain = &userMock{}
Tx = &txMock{}
getUserFromMock = func() (*User, error) {
return &User{ID: 3}, nil
}
user, err := GetByID()
if user.ID != 3 {
t.Fatalf("error")
}
if err != nil {
t.Fatalf("error")
}
}
However, When I try to testing GetByIDWithTx, I cannot assert the mock values:
func TestGetByIDWithTx(t *testing.T) {
userDomain = &userMock{}
Tx = &txMock{}
getUserFromMock = func() (*User, error) {
return &User{ID: 4}, nil
}
/**** Mocking callback response ****/
getFromMock = func() error {
return nil
}
user, err := GetByIDWithTx()
if user.ID != 4 {
t.Fatalf("error") /** It fails here **/
}
if err != nil {
t.Fatalf("error")
}
}
I assume, at the moment I'm trying to assert the callback is still running in another routine but I'm just speculating, I don't have any idea what is going on here. I rather not use any library for a spy, I just want to understand how to solve this. Thoughts?
Go PlayGround HERE
You are never calling the fn given to the txmock. Change txMock.Exec to:
type txMock struct{}
func (t *txMock) Exec(fn func() error) error {
return fn()
// return getFromMock()
}
and remove getFromMock() function completely. It will then call getUserFromMock function to get the mocked user.

How do I mock a function that write result to it's argument in Go

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

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.