App engine Local Unit test different instances [GO] - unit-testing

I have an issue with testing separate methods, each test case is running on a different instance and address.
I'm looking for a way to set up the API address in order to preform the tests on the same API server.
I assuming that this warning is part of the issue.
WARNING 2015-11-04 18:15:25,003 devappserver2.py:779] DEFAULT_VERSION_HOSTNAME will not be set correctly with --port=0
This command will set the API server but I can't do the same for test...
dev_appserver.py . --api_port 55555

Using aetest.NewInstance you can make sure that all your unit tests share a single instance:
var inst aetest.Instance
func TestMain(m *testing.M) {
var err error
inst, err = aetest.NewInstance(nil)
if err != nil {
log.Fatalf("aetest.NewInstance: %v", err)
}
e := m.Run()
inst.Close()
os.Exit(e)
}
func TestMyTest(t *testing.T) {
req, err := inst.NewRequest("GET", "/foo/bar", nil)
// etc.
}

Related

How to mock the mongoDB client in golang?

We have written generic method to connect the MongoDB in golang. Now we want to write the unit testing to mock the DB call. So we need mock the mongo.client.
In Python, we have pymongo lib to mock db call. Similarly do we have any lib in golang?
Or
Do we have any other option to avoid the DB call for unit test implementation?
func RunQuery(collec string, filter bson.M, client *mongo.Client) (result []bson.M, err error) {
collection := client.Database("TESTDB").Collection(collec)
cur, err := collection.Find(ctx, filter)
}
I want to write unit test for method "getuserdetails".
func Test_getUserdetails(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
defer mt.Close()
mt.Run("success", func(mt *mtest.T) {
usercollection := mt.Coll
getuserdetails(usercollection.Database().Client())
})
}
Error:
Message: "no responses remaining"
Labels : NetworkError

DB mock using sqlmock not working for testing Gin/GORM API?

I've an API written using Gin that uses GORM for ORM. The API works perfectly fine when using a real DB and accessing the API URL from the web browser. But I can't get a mocked unit test to pass:
func TestRespForGetUsersHandlerWithSomeUsers(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal("can't create mock db")
}
defer db.Close()
sqlmock.NewRows(
[]string{"id", "name", "username"},
).
AddRow(1, "Abhishek Kumar", "abhishek")
w := httptest.NewRecord()
c, _ := gin.CreateTestContext(w)
postgresDB := postgres.New(postgres.Config{Conn: db})
gormDB, err := gorm.Open(postgresDB, &gorm.Config{})
if err != nil {
t.Fatal("can't create gormDB")
}
api.GetUsersWrapper(gormDB)(c)
if w.Code != http.StatusOK {
t.Errorf("Expected status code to be %d but got %d", http.StatusOK, w.Code)
}
var got []models.User
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatalf("Can't unmarshal response body: %s", err)
}
if len(got) != 1 {
t.Errorf("Expected response to be 1 item but got %d items", len(got))
}
}
The last if-statement gets triggered. The length of got is actually 0. However, if I try calling the API endpoint associated with GetUsersWrapper from a browser (while the server is using a real DB), everything works as expected.
I suspect that either sqlmock.NewRows is not creating the rows such that it'll be visible to gormDB or I'm not testing the response from GetUsersWrapper properly. How can I unit test a gin API correctly?
If you're willing to try an alternate approach, you can unit test your gin APIs using keploy. It's open-source and also supports GORM.
https://github.com/keploy/keploy

How do I test my handler which require tokens to call data services

This is the code written in the handler, which gets the token required to call the data service.
m2m, err := h.getM2MToken(ctx)
if err != nil {
return lc.SetResponse(&events.APIGatewayV2HTTPResponse{
StatusCode: http.StatusInternalServerError,
Body: "Internal Server Error (m2m)",
})
}
//Get the bearer token
userToken, err := h.getBearer(req.Headers)
if err != nil {
xray.AddError(ctx, err)
return lc.SetResponse(&events.APIGatewayV2HTTPResponse{
StatusCode: http.StatusInternalServerError,
Body: "Internal Server Error (bearer)",
})
}
My suggestion is to first try abstracting the inputs that you sent to a method
Like instead of this
userToken, err := h.getBearer(req.Headers)
You can pass specify interfaces like
type userTokenInput struct {}
uti := userTokenInput{}
userToken, err := h.getBearer(uti)
The above helps you to have control over input which makes testing easier
For network calls try using some mock HTTP client which can return expected
data you can follow this for mock HTTP client https://www.thegreatcodeadventure.com/mocking-http-requests-in-golang/
If the service does not work without a token, you will have to provide one.
If the calls you will be doing should not be seen on the real target system for whatever reason, you will need a different target system for testing.
Ask the provider if they have a test installation you can use.
Consider testing against a mock.

How to mock a method of a third party package

I had a simple function which connects to mongoDB and create a new document.
Now how do I mock the methods of the imported mongo package while unit testing.
Ive tried to mock GinContext by monkeypatching.
But unable to proceed with mocking the actual mongoClient as the package is imported.
func CreateUser(c GinContext) {
var userdetail UserDetails
binderr := c.ShouldBindJSON(&userdetail)
fmt.Println(binderr)
if binderr != nil {
c.JSON(500, gin.H{
"message": "Input payload not matching",
"error": binderr,
})
return
}
//-- Client if of type *mongo.Client.
//-- How do I mock the Client.Database, Client.Database.Connection
collection := Client.Database("demo").Collection("users")
ctx, err1 := context.WithTimeout(context.Background(), 10*time.Second)
if err1 != nil {
}
response, err2 := collection.InsertOne(ctx, userdetail)
if err2 != nil {
log.Println("Some error inserting the document")
}
fmt.Println(response.InsertedID)
c.JSON(200, gin.H{
"message": "User created successfully",
})
}
Expected: I should be able to mock or stub Client and provide dummy functionality. Just like in nodeJS we do
spyOn(Client,'Database').and.return(Something)
Every time I'm wondering "how to mock a method", this is mostly related to my code architecture. Not being able to test easily some code means, most of time, that the code is poorly designed and/or too coupled to the used libraries/frameworks. Here, you want to mock Mongo connection only because your code is too tightly related to Mongo (in the CreateUser function). Refactoring could help you to test your code (without any Mongo connection).
I've experienced that using interfaces and dependency injection simplifies
the testing process in Go, and clarifies the architecture. Here is my attempt to help you test your application.
Code refactoring
First, define what you want to do with an interface. Here, you're inserting users, so let's do a UserInserter interface, with a single method for now (Insert, to insert a single user) :
type UserInserter interface {
Insert(ctx context.Context, userDetails UserDetails) (insertedID interface{}, err error)
}
In the code you have provided, you are only using the insertedID, so you probably only need it as output of this Insert method (and an optional error if something gone wrong). insertedID is defined as an interface{} here, but feel free to change to whatever you want.
Then, let's modify your CreateUser method and inject this UserInserter as a parameter :
func CreateUser(c *gin.Context, userInserter UserInserter) {
var userdetail UserDetails
binderr := c.ShouldBindJSON(&userdetail)
fmt.Println(binderr)
if binderr != nil {
c.JSON(500, gin.H{
"message": "Input payload not matching",
"error": binderr,
})
return
}
// this is the modified part
insertedID, err2 := userInserter.Insert(c, userdetail)
if err2 != nil {
log.Println("Some error inserting the document")
}
fmt.Println(insertedID)
c.JSON(200, gin.H{
"message": fmt.Sprintf("User %s created successfully", insertedID),
})
}
This method could be refactored but, to avoid any confusion, I will not touch it.
userInserter.Insert(c, userdetail) replaces here the Mongo dependency in this method by injecting userInserter.
You can now implement your UserInserter interface with the backend of your choice (Mongo in your case). Insertion into Mongo needs a Collection object (the collection we are inserting the user in), so let's add this as an attribute :
type MongoUserInserter struct {
collection *mongo.Collection
}
Implementation of Insert method follows (call InsertOne method on *mongo.Collection) :
func (i MongoUserInserter) Insert(ctx context.Context, userDetails UserDetails) (insertedID interface{}, err error) {
response, err := i.collection.InsertOne(ctx, userDetails)
return response.InsertedID, err
}
This implementation could be in a separated package and should be tested separately.
Once implemented, you can use MongoUserInserter in your main application, where Mongo is the backend. MongoUserInserter is initialized in the main function, and injected in the CreateUser method. Router setup have been separated (also for testing purpose) :
func setupRouter(userInserter UserInserter) *gin.Engine {
router := gin.Default()
router.POST("/createUser", func(c *gin.Context) {
CreateUser(c, userInserter)
})
return router
}
func main() {
client, _ := mongo.NewClient()
collection := client.Database("demo").Collection("users")
userInserter := MongoUserInserter{collection: collection}
router := setupRouter(userInserter)
router.Run(":8080")
}
Note that if some day you want to change the backend, you will only
need to change the userInserter in the main function!
Tests
From a tests perspective, it is now easier to test because we can create a fake UserInserter, like :
type FakeUserInserter struct{}
func (_ FakeUserInserter) Insert(ctx context.Context, userDetails UserDetails) (insertedID interface{}, err error) {
return userDetails.Name, nil
}
(I supposed here UserDetails have an attribute Name).
If you really want to mock this interface, you can take a look at GoMock. In this case though, I'm not sure using a mock framework is required.
And now we can test our CreateUser method with a simple HTTP testing framework (see https://github.com/gin-gonic/gin#testing), without needing a Mongo connection or mocking it.
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateUser(t *testing.T) {
userInserter := FakeUserInserter{}
router := setupRouter(userInserter)
w := httptest.NewRecorder()
body := []byte(`{"name": "toto"}`)
req, _ := http.NewRequest("POST", "/createUser", bytes.NewBuffer(body))
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.Equal(t, `{"message":"User toto created successfully"}`, w.Body.String())
}
Note that this does not exempt to also test Insert method of MongoUserInserter, but separately : here, this test covers CreateUser, not the Insert method.

Proper testing of http routes in go

I have 5 end points which have methods such as GET, POST, and DELETE to test. I wrote test cases using the go's in built testing package. I'm worried that I'm missing some cases which are not striking to my mind.I have posted in code review for my test case to be reviewed but I didn't get any response. I have also followed this post Testing HTTP routes in golang. All these test cases are checking for the response codes.
The problem is that, most of my test cases follow similar pattern where I post data in different formats and checking the response codes. I strongly feel like I'm missing something that will break my API when I push it to prod. I need some insight on testing these routes so that I can be confident to push the api to prod.
main_test.go
func TestSigHandler(t *testing.T){
test_cases := []string{"2021205"}
// GET Testing
for _, method := range test_cases{
usersUrl = fmt.Sprintf("%s/1/sig/id/%s", server.URL, method) //Grab the address for the API endpoint
request, err := http.NewRequest("GET", usersUrl, nil)
res, err := http.DefaultClient.Do(request)
if err != nil {
t.Error(err) //Something is wrong while sending request
}
if res.StatusCode != 200 {
t.Errorf("Something went wrong : ", res.StatusCode) //Uh-oh this means our test failed
}
}
// POST Testing
sig := []byte( `{
"raw": "a new sig"
}`)
usersUrl = fmt.Sprintf("%s/1/sig/id/2021205", server.URL) //Grab the address for the API endpoint
request, err := http.NewRequest("POST", usersUrl, bytes.NewBuffer(sig))
if err != nil{
t.Error(err)
}
request.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(request)
if err != nil {
t.Error(err) //Something is wrong while sending request
}
if res.StatusCode != 200 {
t.Errorf(" Something Went Wrong: ", res.StatusCode) //Uh-oh this means our test failed
}
// DELETE Testing
sigs_delete_cases := []string{ "1000345"}
for _, sig_to_be_deleted := range sigs_delete_cases{
usersUrl = fmt.Sprintf("%s/1/sig/id/%s", server.URL, sig_to_be_deleted) //Grab the address for the API endpoint
request, err := http.NewRequest("DELETE", usersUrl, nil)
res, err := http.DefaultClient.Do(request)
if err != nil {
t.Error(err) //Something is wrong while sending request
}
if res.StatusCode != 200 {
t.Errorf("Tried to delete a reserved Id : ", res.StatusCode) //Uh-oh this means our test failed
}
}
}
I like to do this way:
Establish Continuous Integration. If your project is Open Source, you may use services like Travis CI - it has very easy installation. This helps you to see how changes affect code.
Set code test coverage. It allows you to see what source code lines are covered with tests and what are not and where very possible bugs will emerge. Of course, code coverage tool is not a panacea. And if line was checked it doesn't mean it is absolutely ok, and it will not fail with other input. But it helps much to maintain good code and look for bugs. For open source you may use coveralls.io. There's a special goveralls plugin for it.
To help the problem above you may use so-called Fuzzy testing - exploratory tests with random input to find a root cause. There're standard https://golang.org/pkg/testing/quick/ and non-standard packages https://github.com/dvyukov/go-fuzz.
Then I experiment with tests, they are both positive and negative. I try check situation with errors, timeouts, incorrect replies.
For my tests I've used as usual client http so httptest package.