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...]
}
Related
I've mocked a repository interface and I want to return a specific value when a method is called. However, it always returns what's in the dummy implementation.
type MockUserRepo struct {
mock.Mock
}
// dummy method to fulfil the interface
func (m *MockUserRepo) FindByUsername(username string) (*User, error) {
return nil, nil
}
Now, I setup the mock as such
m := NewMockUserRepo()
m.On("FindByUsername", mock.Anything).Return(&User{
Username: "test"
}, nil)
// inject mock
svc := NewService(m)
user, err := svc.FindByUsername("anything") // always nil,nil
The return value (user, err) is always nil (or whatever is returned in MockUserRepo.FindByUsername
Is there something I'm doing wrong?
source : https://pkg.go.dev/github.com/stretchr/testify/mock#hdr-Example_Usage
For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine)
So in your FindByUsername method change to this:
func (m *MockUserRepo) FindByUsername(username string) (*User, error) {
args := m.Called(username)
return args.Get(0).(*User), args.Error(1)
}
playground
I have the following function that makes multiple calls to AWS IAM. I am able to run unit tests on single calls. However when I run a test on the one below I get a panic: "runtime error, invalid memory or nil pointer dereference"
func (iamDependency *iamService) CreateMyUser(userName string) (string, error){
//first IAM call
err:=iamDependency.GetUser(&iam.GetUserInput{UserName: userName})
if err != nil {
fmt.Println("Failed to get user, attempting to create")
//second IAM call
err:=iamDependency.CreateUser(&iam.CreateUserInput{UserName: userName})
if err != nil {
log.Fatalf("Failed to create user\n", err )
}
}
}
Here is my mock and test:
type mockSomeOutput{}
type mockedIamCall{
iamiface.IAMAPI
Response mockSomeOutput
}
func TestCreateMyUser(t *testing.T){
t.Run("Successful user create", fun(t *testing.T){
mo:= mockOutput{}
m:= mockedIamCall{Response: mo}
d:= iamService{
iamInstance: m,
}
mockedUser:="TestUser"
_, err:= d.ResetCredentials(&mockedUser)
if err != nil {
t.Fatal("Everything should be ok")
}
})
}
I'm wondering whether there are any tricks or guidelines for making unit tests for this kind of function in Golang.
Appreciate any help.
You probably want to try using: https://github.com/golang/mock
You can creating mock implementation for the iamiface.IAMAPI (from the actual interface) then expecting the function calls and mocking the response.
Creating the mock implementation of the interface using mockgen.
mockgen -source={path to IAM API interface}
And then you can expect the function calls with something like this on the test cases:
function TestExample(t *testing.T) {
ctrl := gomock.NewController(t)
mockIAMAPI := mock_package.NewMockIAMAPI(ctrl)
mockIAMAPI.EXPECT().GetUser(expectedInput).Return(mockResponse).Times(1)
}
#Raymond thanks for the response, it was insightful. However I seem to have found a simpler answer to my own question. I created my own interface
type UserCreator interface{
GetUser(*iam.GetUserInput) (*iam.GetUserOutput, error)
CreateUser(*iam.CreateUserInput) (*iam.CreateUserInput, error)
}
func CreateMyUser(iamSvc UserCreator, userName string) (string, error){
//first IAM call
_, err:=iamSvc.GetUser(&iam.GetUserInput{UserName: userName})
if err != nil {
fmt.Println("Failed to get user, attempting to create")
//second IAM call
_, err:=iamSvc.CreateUser(&iam.CreateUserInput{UserName: userName})
if err != nil {
log.Fatalf("Failed to create user\n", err )
}
}
}
And then for my test I just implement the interface, override these methods, and pass a mock:
type mockUserCreator{
Response string
}
func (m * mockUserCreator) GetUser(input *iam.GetUserInput)(*iam.GetUserOutput, error){
return &iam.GetUserOutput{}, nil
}
func (m * mockUserCreator) CreateUser(input *iam.CreateUserInput)(*iam.CreateUserOutput, error){
return &iam.CreateUserOutput{}, nil
}
func TestCreateMyUser(t *testing.T){
testcases:=[]struct{
TestName string
}{
{
TestName:"Some test"
}
}
for _, tt := range testcases{
t.Run(tt.TestName, func(t *testing.T){
m := mockUserCreator{}
mockUser := "TestUser"
_, err:= CreateMyUser(&m, mockUser)
if err != nil {
t.Error("TestCreateMyUser returned and error: %s", err)
}
}
}
}
I need to create a Pull Request comment using go-github, and my code works, but now I'd like to write tests for it (yes, I'm aware that tests should come first), so that I don't actually call the real GitHub service during test.
I've read 3 blogs on golang stubbing and mocking, but, being new to golang, I'm a bit lost, despite this discussion on go-github issues. For example, I wrote the following function:
// this is my function
func GetClient(token string, url string) (*github.Client, context.Context, error) {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)
client, err := github.NewEnterpriseClient(url, url, tc)
if err != nil {
fmt.Printf("error creating github client: %q", err)
return nil, nil, err
}
return client, ctx, nil
}
How could I stub that?
Similarly, I have this:
func GetPRComments(ctx context.Context, client *github.Client) ([]*github.IssueComment, *github.Response, error) {
opts := &github.IssueListCommentsOptions{
ListOptions: github.ListOptions{
Page: 1,
PerPage: 30,
},
}
githubPrNumber, err := strconv.Atoi(os.Getenv("GITHUB_PR_NUMBER"))
if err != nil || githubPrNumber == 0 {
panic("error: GITHUB_PR_NUMBER is not numeric or empty")
}
// use Issues API for PR comments since GitHub docs say "This may seem counterintuitive... but a...Pull Request is just an Issue with code"
comments, response, err := client.Issues.ListComments(
ctx,
os.Getenv("GITHUB_OWNER"),
os.Getenv("GITHUB_REPO"),
githubPrNumber,
opts)
if err != nil {
return nil, nil, err
}
return comments, response, nil
}
How should I stub that?
My thought was to perhaps use dependency injection by creating my own structs first, but I'm not sure how, so currently I have this:
func TestGetClient(t *testing.T) {
client, ctx, err := GetClient(os.Getenv("GITHUB_TOKEN"), "https://example.com/api/v3/")
c, r, err := GetPRComments(ctx, client)
...
}
I would start with an interface:
type ClientProvider interface {
GetClient(token string, url string) (*github.Client, context.Context, error)
}
When testing a unit that needs to call GetClient make sure you depend on your ClientProvider interface:
func YourFunctionThatNeedsAClient(clientProvider ClientProvider) error {
// build you token and url
// get a github client
client, ctx, err := clientProvider.GetClient(token, url)
// do stuff with the client
return nil
}
Now in your test, you can construct a stub like this:
// A mock/stub client provider, set the client func in your test to mock the behavior
type MockClientProvider struct {
GetClientFunc func(string, string) (*github.Client, context.Context, error)
}
// This will establish for the compiler that MockClientProvider can be used as the interface you created
func (provider *MockClientProvider) GetClient(token string, url string) (*github.Client, context.Context, error) {
return provider.GetClientFunc(token, url)
}
// Your unit test
func TestYourFunctionThatNeedsAClient(t *testing.T) {
mockGetClientFunc := func(token string, url string) (*github.Client, context.Context, error) {
// do your setup here
return nil, nil, nil // return something better than this
}
mockClientProvider := &MockClientProvider{GetClientFunc: mockGetClientFunc}
// Run your test
err := YourFunctionThatNeedsAClient(mockClientProvider)
// Assert your result
}
These ideas aren't my own, I borrowed them from those who came before me; Mat Ryer suggested this (and other ideas) in a great video about "idiomatic golang".
If you want to stub the github client itself, a similar approach can be used, if github.Client is a struct, you can shadow it with an interface. If it is already an interface, the above approach works directly.
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.
I've built a quick and easy API in Go that queries ElasticSearch. Now that I know it can be done, I want to do it correctly by adding tests. I've abstracted some of my code so that it can be unit-testable, but I've been having some issues mocking the elastic library, and as such I figured it would be best if I tried a simple case to mock just that.
import (
"encoding/json"
"github.com/olivere/elastic"
"net/http"
)
...
func CheckBucketExists(name string, client *elastic.Client) bool {
exists, err := client.IndexExists(name).Do()
if err != nil {
panic(err)
}
return exists
}
And now the test...
import (
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"testing"
)
type MockClient struct {
mock.Mock
}
func (m *MockClient) IndexExists(name string) (bool, error) {
args := m.Mock.Called()
fmt.Println("This is a thing")
return args.Bool(0), args.Error(1)
}
func TestMockBucketExists(t *testing.T) {
m := MockClient{}
m.On("IndexExists", "thisuri").Return(true)
>> r := CheckBucketExists("thisuri", m)
assert := assert.New(t)
assert.True(r, true)
}
To which I'm yielded with the following error: cannot use m (type MockClient) as type *elastic.Client in argument to CheckBucketExists.
I'm assuming this is something fundamental with my use of the elastic.client type, but I'm still too much of a noob.
This is an old question, but couldn't find the solution either.
Unfortunately, this library is implemented using a struct, that makes mocking it not trivial at all, so the options I found are:
(1) Wrap all the elastic.SearchResult Methods on an interface on your own and "proxy" the call, so you end up with something like:
type ObjectsearchESClient interface {
// ... all methods...
Do(context.Context) (*elastic.SearchResult, error)
}
// NewObjectsearchESClient returns a new implementation of ObjectsearchESClient
func NewObjectsearchESClient(cluster *config.ESCluster) (ObjectsearchESClient, error) {
esClient, err := newESClient(cluster)
if err != nil {
return nil, err
}
newClient := objectsearchESClient{
Client: esClient,
}
return &newClient, nil
}
// ... all methods...
func (oc *objectsearchESClient) Do(ctx context.Context) (*elastic.SearchResult, error) {
return oc.searchService.Do(ctx)
}
And then mock this interface and responses as you would with other modules of your app.
(2) Another option is like pointed in this blog post that is mock the response from the Rest calls using httptest.Server
for this, I mocked the handler, that consist of mocking the response from the "HTTP call"
func mockHandler () http.HandlerFunc{
return func(w http.ResponseWriter, r *http.Request) {
resp := `{
"took": 73,
"timed_out": false,
... json ...
"hits": [... ]
...json ... ,
"aggregations": { ... }
}`
w.Write([]byte(resp))
}
}
Then you create a dummy elastic.Client struct
func mockClient(url string) (*elastic.Client, error) {
client, err := elastic.NewSimpleClient(elastic.SetURL(url))
if err != nil {
return nil, err
}
return client, nil
}
In this case, I've a library that builds my elastic.SearchService and returns it, so I use the HTTP like:
...
ts := httptest.NewServer(mockHandler())
defer ts.Close()
esClient, err := mockClient(ts.URL)
ss := elastic.NewSearchService(esClient)
mockLibESClient := es_mock.NewMockSearcherClient(mockCtrl)
mockLibESClient.EXPECT().GetEmployeeSearchServices(ctx).Return(ss, nil)
where mockLibESClient is the library I mentioned, and we stub the mockLibESClient.GetEmployeeSearchServices method making it return the SearchService with that will return the expected payload.
Note: for creating the mock mockLibESClient I used https://github.com/golang/mock
I found this to be convoluted, but "Wrapping" the elastic.Client was in my point of view more work.
Question: I tried to mock it by using https://github.com/vburenin/ifacemaker to create an interface, and then mock that interface with https://github.com/golang/mock and kind of use it, but I kept getting compatibility errors when trying to return an interface instead of a struct, I'm not a Go expect at all so probably I needed to understand the typecasting a little better to be able to solve it like that. So if any of you know how to do it with that please let me know.
The elasticsearch go client Github repo contains an official example of how to mock the elasticsearch client. It basically involves calling NewClient with a configuration which stubs the HTTP transport:
client, err := elasticsearch.NewClient(elasticsearch.Config{
Transport: &mocktrans,
})
There are primarily three ways I discovered to create a Mock/Dumy ES client. My response does not include integration tests against a real Elasticsearch cluster.
You can follow this article so as to mock the response from the Rest calls using httptest.Server, to eventually create a dummy elastic.Client struct
As mentioned by the package author in this link, you can work on "specifying an interface that has two implementations: One that uses a real ES cluster, and one that uses callbacks used in testing. Here's an example to get you started:"
type Searcher interface {
Search(context.Context, SearchRequest) (*SearchResponse, error)
}
// ESSearcher will be used with a real ES cluster.
type ESSearcher struct {
client *elastic.Client
}
func (s *ESSearcher) Search(ctx context.Context, req SearchRequest) (*SearchResponse, error) {
// Use s.client to run against real ES cluster and perform a search
}
// MockedSearcher can be used in testing.
type MockedSearcher struct {
OnSearch func(context.Context, SearchRequest) (*SearchResponse, error)
}
func (s *ESSearcher) Search(ctx context.Context, req SearchRequest) (*SearchResponse, error) {
return s.OnSearch(ctx, req)
}
Finally, as mentioned by the author in the same link you can "run a real Elasticsearch cluster while testing. One particular nice way might be to start the ES cluster during testing with something like github.com/ory/dockertest. Here's an example to get you started:"
package search
import (
"context"
"fmt"
"log"
"os"
"testing"
"github.com/olivere/elastic/v7"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
)
// client will be initialize in TestMain
var client *elastic.Client
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("unable to create new pool: %v", err)
}
options := &dockertest.RunOptions{
Repository: "docker.elastic.co/elasticsearch/elasticsearch-oss",
Tag: "7.8.0",
PortBindings: map[docker.Port][]docker.PortBinding{
"9200": {{HostPort: "9200"}},
},
Env: []string{
"cluster.name=elasticsearch",
"bootstrap.memory_lock=true",
"discovery.type=single-node",
"network.publish_host=127.0.0.1",
"logger.org.elasticsearch=warn",
"ES_JAVA_OPTS=-Xms1g -Xmx1g",
},
}
resource, err := pool.RunWithOptions(options)
if err != nil {
log.Fatalf("unable to ES: %v", err)
}
endpoint := fmt.Sprintf("http://127.0.0.1:%s", resource.GetPort("9200/tcp"))
if err := pool.Retry(func() error {
var err error
client, err = elastic.NewClient(
elastic.SetURL(endpoint),
elastic.SetSniff(false),
elastic.SetHealthcheck(false),
)
if err != nil {
return err
}
_, _, err = client.Ping(endpoint).Do(context.Background())
if err != nil {
return err
}
return nil
}); err != nil {
log.Fatalf("unable to connect to ES: %v", err)
}
code := m.Run()
if err := pool.Purge(resource); err != nil {
log.Fatalf("unable to stop ES: %v", err)
}
os.Exit(code)
}
func TestAgainstRealCluster(t *testing.T) {
// You can use "client" variable here
// Example code:
exists, err := client.IndexExists("cities-test").Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !exists {
t.Fatal("expected to find ES index")
}
}
The line
func CheckBucketExists(name string, client *elastic.Client) bool {
states that CheckBucketExists expects a *elastic.Client.
The lines:
m := MockClient{}
m.On("IndexExists", "thisuri").Return(true)
r := CheckBucketExists("thisuri", m)
pass a MockClient to the CheckBucketExists function.
This is causing a type conflict.
Perhaps you need to import github.com/olivere/elastic into your test file and do:
m := &elastic.Client{}
instead of
m := MockClient{}
But I'm not 100% sure what you're trying to do.