I have the following production code:
func pullMessages(ctx context.Context, sub *pubsub.Subscription) {
err := sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
log.Printf("Got message: %q\n", string(msg.Data))
processMessage(msg)
msg.Ack()
})
if err != nil {
log.Printf("Receive: %v", err)
}
}
How can I verify processMessage is actually being called in a unit test ?
Quite simple: make your callback a named function instead of an anonymous one:
func pullMessages(ctx context.Context, sub *pubsub.Subscription) {
if err := sub.Receive(ctx, rcvCallback); err != nil {
log.Printf("Receiving message: %s",err)
}
}
func rcvCallback (ctx context.Context, msg *pubsub.Message) {
log.Printf("Got message: %q\n", string(msg.Data))
processMessage(msg)
msg.Ack()
}
Now, in your unit tests, you can create an instance of a Context and a Message and pass it to your function. However, in this case, that barely makes sense. You do little more than logging and acknowledging the message, functions which should be unit tested by the upstream project. Hence it would make more sense to construct an instance of message and unit test processMessage.
Related
I am trying to write unit test for my app. I am using fasthttp lib. I also use fasthttp-routing lib. So the problem is that my handler is not standard type of fasthttp.HandlerFunc but routing.Handler.
In order to test HTTP handlers i've written the function that accepts handler fasthttp.RequestHandler parameter. The lib method fasthttp.Serve() accepts handler with type fasthttp.RequestHandler. I use this method to serve incoming connections from the given listener using the given handler. But my handler is of type routing.Handler
My handler:
func deleteExampleBOById(c *routing.Context) error { // Some logic }
My serve() function that i use to serve connections in order to unit test handlers:
func serve(handler fasthttp.RequestHandler, req *http.Request) (*http.Response, error) {
ln := fasthttputil.NewInmemoryListener()
defer ln.Close()
go func() {
err := fasthttp.Serve(ln, handler)
if err != nil {
panic(fmt.Errorf("failed to serve: %v", err))
}
}()
client := http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return ln.Dial()
},
},
}
return client.Do(req)
}
My actual unit test:
func TestHandler(t *testing.T) {
r, err := http.NewRequest("GET", "http://localhost:8181/GoService/example/v1/1", nil)
if err != nil {
t.Error(err)
}
res, err := serve(getExampleBOById, r)
if err != nil {
t.Error(err)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
fmt.Println(string(body))
}
I am not able to serve my handler using function fasthttp.serve(), because of signature differences. I would like to ask any ideas how to convert routung.Handler to fasthttp.HandlerFunc or any other ideas how to unit test my handlers.
I don't have ideas how to solve it
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...]
}
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'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.