I have a handlerAuthentication function that I need to test:
func handlerAuthentication(c *gin.Context) {
session := Session.GetSession(c)
var login Login
err := c.BindJSON(&login)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
client, err := initClient(c, login)
fmt.Println("Error: ",err)
if err != nil {
fmt.Println("There's an error !")
c.JSON(http.StatusUnauthorized, gin.H{"error": ErrorWrongLogin})
return
}
err = (*client).Logout(c)
if err != nil {
return
}
session.Set("username", login.Username)
session.Set("password", login.Password)
err = session.Save()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "an error occurred during the save of the session:" + err.Error()})
return
}
c.JSON(http.StatusOK, "Connected")
}
To do so,I made this:
func TestHandlerAuthentication(t *testing.T) {
UrlOdoo = "https://isi.nc"
resp := httptest.NewRecorder()
gin.SetMode(gin.TestMode)
c, r := gin.CreateTestContext(resp)
r.POST("/test", func(c *gin.Context) {
handlerAuthentication(c)
})
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Odoo = OdooRPC{createMockOdooClient}
client = mock_odoorpc.NewMockOdooClient(ctrl)
client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), invalidUsername, invalidPassword).AnyTimes().Return(fmt.Errorf("invalid login"))
client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), validUsername, validPassword).AnyTimes().Return(nil)
client.EXPECT().Logout(gomock.Any()).AnyTimes().Return(nil)
session = mock_session.NewMockSession(ctrl)
Session = SessionGetter{createMockSession}
session.EXPECT().Set("username", validUsername).AnyTimes().Return()
session.EXPECT().Set("password", validPassword).AnyTimes().Return()
session.EXPECT().Save().AnyTimes().Return(nil)
for name, test := range map[string]struct {
input Login
want int
}{
"valid login": {
input: Login{
Username: validUsername,
Password: validPassword,
},
want: 200,
},
"invalid login": {
input: Login{
Username: invalidUsername,
Password: invalidPassword,
},
want: 401,
},
} {
t.Run(name, func(t *testing.T) {
body, _ := json.Marshal(test.input)
c.Request, _ = http.NewRequest(http.MethodPost, "/test", strings.NewReader(string(body)))
r.ServeHTTP(resp, c.Request)
assert.Equal(t, test.want, resp.Code)
resp.Flush()
})
}
}
The problem I'm facing is that if I do the tests one by (valid login and invalid login), they all pass, but when I do the two tests at the same time, the second test fails.
Here's an exemple of execution of the two tests at the same time:
=== RUN TestHandlerAuthentication
=== RUN TestHandlerAuthentication/valid_login
Error: <nil> //No error, so resp.Code should be equal to 200
=== RUN TestHandlerAuthentication/invalid_login
Error: invalid login //Error, so resp.Code should be equal to 401
There's an error !
main_test.go:394:
Error Trace: main_test.go:394
Error: Not equal:
expected: 401
actual : 200
Test: TestHandlerAuthentication/invalid_login
--- FAIL: TestHandlerAuthentication (0.00s)
--- PASS: TestHandlerAuthentication/valid_login (0.00s)
--- FAIL: TestHandlerAuthentication/invalid_login (0.00s)
Expected :401
Actual :200
As expected, an error occured when the login is invalid, but the resp.Code is still 200.
And if I do the invalid login test first, the resp.Code will still be 401.
Is it happening because the tests are parallelized and the httptest ResponseRecorder doesn't work in parallel ?
Thank you for your help.
Thank you leaf bebop
I needed to initialize a new httptest.ResponseRecorder for each test.
To do so, I move the initialisation to the t.Run(name,func(t *testing.T) function:
func TestHandlerAuthentication(t *testing.T) {
UrlOdoo = "https://isi.nc"
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Odoo = OdooRPC{createMockOdooClient}
client = mock_odoorpc.NewMockOdooClient(ctrl)
client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), invalidUsername, invalidPassword).AnyTimes().Return(fmt.Errorf("invalid login"))
client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), validUsername, validPassword).AnyTimes().Return(nil)
client.EXPECT().Logout(gomock.Any()).AnyTimes().Return(nil)
session = mock_session.NewMockSession(ctrl)
Session = SessionGetter{createMockSession}
session.EXPECT().Set("username", validUsername).AnyTimes().Return()
session.EXPECT().Set("password", validPassword).AnyTimes().Return()
session.EXPECT().Save().AnyTimes().Return(nil)
for name, test := range map[string]struct {
input Login
want int
}{
"valid login": {
input: Login{
Username: validUsername,
Password: validPassword,
},
want: 200,
},
"invalid login": {
input: Login{
Username: invalidUsername,
Password: invalidPassword,
},
want: 401,
},
} {
t.Run(name, func(t *testing.T) {
resp := httptest.NewRecorder()
gin.SetMode(gin.TestMode)
c, r := gin.CreateTestContext(resp)
r.POST("/test", func(c *gin.Context) {
handlerAuthentication(c)
})
body, _ := json.Marshal(test.input)
c.Request, _ = http.NewRequest(http.MethodPost, "/test", strings.NewReader(string(body)))
r.ServeHTTP(resp, c.Request)
assert.Equal(t, test.want, resp.Code)
})
}
}
Related
I have a user service that validates the user data and formats it and then calls Firebase service which creates a firebase user and return firebase id and then it pass that user data to repository layer. My user struct has an ID field is populated by uuid in user service before passing to repository layer. I am mocking the firebase service and repository layer using strecher/testify. But the test is failing since the ID is populated by service layer and I cannot pass the ID to the user data used by mock function.
user := model.User{
ID: "",
FirebaseID: "",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com"
Password: "password",
}
Service layer code
func (u userService) CreateUser(user model.User) error {
err := validateFields(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.FullName = user.FirstName + " " + user.LastName
user.FirebaseID, err = u.authClient.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.ID = uuid.NewString()
err = u.userRepo.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
return nil
}
Test code
func TestCreateUser(t *testing.T) {
mockFirebaseAuthClient := new(MockFirebaseAuthClient)
mockPostgresRepo := new(MockPostgresRepo)
userService := NewUserService(mockPostgresRepo, mockFirebaseAuthClient)
t.Run("Valid data", func(t *testing.T) {
user := model.User{
ID: "",
FirebaseID: "firebaseuniqueid",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com",
Password: "password",
}
mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil)
mockPostgresRepo.On("CreateUser", user).Return(nil)
err := userService.CreateUser(user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
})
Error while testing
mock: Unexpected Method Call
-----------------------------
CreateUser(model.User)
0: model.User{ID:"f87fd2f3-5801-4359-a565-a4eb13a6de37", FirebaseID:"firebaseuniqueid", FirstName:"Test", LastName:"User", FullName:"Test User", Email:"testuser#email.com", Password:"password"}
The closest call I have is:
CreateUser(model.User)
0: model.User{ID:"", FirebaseID:"firebaseuniqueid", FirstName:"Test", LastName:"User", FullName:"Test User", Email:"testuser#email.com", Password:"password"}
Difference found in argument 0:
--- Expected
+++ Actual
## -1,3 +1,3 ##
(model.User) {
- ID: (string) "",
+ ID: (string) (len=36) "f87fd2f3-5801-4359-a565-a4eb13a6de37",
FirebaseID: (string) (len=16) "firebaseuniqueid",
Diff: 0: FAIL: (model.User={f87fd2f3-5801-4359-a565-a4eb13a6de37 firebaseuniqueid Test User Test User testuser#email.com password}) != (model.User={ firebaseuniqueid Test User Test User testuser#email.com password}) [recovered]
Is there any way I could check the dynamically created uuid or ignore the values in the struct in the test?
if you don't want to consider mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil) and mockPostgresRepo.On("CreateUser", user).Return(nil) and just want to mock that calls, then you can use mock.Anything as the argument in both the calls instead of user like this mockFirebaseAuthClient.On("CreateUser", mock.Anything).Return("firebaseuniqueid", nil) . So the arguments will not be considerd and the mock calls will return required value.
Regarding your question of
Since uuid is not injected into the service layer, how can it be mocked? It is an imported package.
Like this, first, define an interface with the same method we want to mock
type uuidGen interface {
String() string
}
Then, define a mock type in which we're going to define our method
type mockGen struct{}
Then, define the method on the type
func (u *mockGen) String() string {
return "test"
}
Update CreateUser function to receive a uuidGen parameter that shares the method String() with uuid's package.
func (u userService) CreateUser(uuid uuidGen, user User) error {
err := validateFields(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.FullName = user.FirstName + " " + user.LastName
user.FirebaseID, err = u.authClient.CreateUser(user)
if err != nil {
return fmt.Errorf("authClient CreateUser: %w", err)
}
user.ID = uuid.String()
err = u.userRepo.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
return nil
}
Now we can write the test like this, see how the 2 methods accept different types that implement the interface uuidGen and can call a method String()
func TestCreateUser(t *testing.T) {
mockFirebaseAuthClient := new(MockFirebaseAuthClient)
mockPostgresRepo := new(MockPostgresRepo)
userService := NewUserService("test", "test")
t.Run("Valid data", func(t *testing.T) {
user := User{
ID: "",
FirebaseID: "firebaseuniqueid",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com",
Password: "password",
}
mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil)
mockPostgresRepo.On("CreateUser", user).Return(nil)
gen := new(mockGen)
err := userService.CreateUser(gen, user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
realUUID := uuid.New()
err = userService.CreateUser(realUUID, user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
t.Log("Mock UUID:", gen.String()) // prints test
t.Log("Real UUID UUID:", realUUID.String()) // prints a UUID
})
}
Run it with go test -v to see the output of t.Log(...)
Go here. Trying to figure out how to use SQL mock v2.
Here's my interface:
type OrderPersister interface {
FetchOrderById(string) (*Order, error)
}
And my implementation of that interface:
type DbPersister struct {
Config config.DbConfig
GormDB *gorm.DB
}
func (op DbPersister) FetchOrderById(orderId string) (*Order, error) {
Order := &Order{}
orderUuid, err := uuid.Parse(orderId)
if err != nil {
return nil, err
}
if err := op.GormDB.Table("orders").
Select(`orders.order_id,
orders.user_id,
orders.quantity,
orders.status
addresses.line_1,
users.email`).
Joins("join addresses on addresses.address_id = orders.address_id").
Joins("join users on users.user_id = orders.user_id").
Where("orders.order_id = ?", orderUuid).
First(Order).Error; err != nil {
return nil, err
}
return Order, nil
}
And my unit test (including setup/init):
import (
"database/sql"
"testing"
"github.com/google/uuid"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gopkg.in/DATA-DOG/go-sqlmock.v2"
)
type Suite struct {
suite.Suite
DB *gorm.DB
mock sqlmock.Sqlmock
dbPersister OrderPersister
}
func (s *Suite) SetupSuite() {
var (
db *sql.DB
err error
)
db, s.mock, err = sqlmock.New()
require.NoError(s.T(), err)
s.DB, err = gorm.Open("postgres", db)
require.NoError(
s.T(), err)
s.DB.LogMode(true)
s.dbPersister = DbPersister{
Config: config.DbConfig{
DbHost: "",
DbPort: "",
DbName: "",
DbUsername: "",
DbPassword: "",
},
GormDB: s.DB,
}
}
func (s *Suite) BeforeTest(_, _ string) {
var (
db *sql.DB
err error
)
db, s.mock, err = sqlmock.New()
require.NoError(s.T(), err)
s.DB, err = gorm.Open("postgres", db)
require.NoError(s.T(), err)
s.DB.LogMode(true)
}
func (s *Suite) AfterTest(_, _ string) {
require.NoError(s.T(), s.mock.ExpectationsWereMet())
}
func TestInit(t *testing.T) {
suite.Run(t, new(Suite))
}
func (s *Suite) TestFetchOrderById() {
// given
orderId := uuid.New()
quantity := 1
status := "ready"
line1 := "201"
email := "jsmith#example.com"
// s.mock.ExpectBegin()
s.mock.ExpectQuery(`SELECT`).
WillReturnRows(sqlmock.NewRows([]string{"orders.order_id","orders.user_id","orders.quantity","orders.status",
"addresses.line_1","user_logins.email"}).
AddRow(sqlmock.AnyArg(), sqlmock.AnyArg(), quantity, status, totalExclTax, shippingExclTax,
totalTaxAmt, line1, state, zip, locality, upc, email, firstName, lastName))
_, err := s.dbPersister.FetchOrderById(orderId.String())
s.mock.ExpectCommit()
require.NoError(s.T(), err)
}
When this runs the test fails for the following reason:
--- FAIL: TestInit (0.00s)
--- FAIL: TestInit/TestFetchOrderById (0.00s)
db_test.go:67:
Error Trace: db_test.go:67
suite.go:137
panic.go:969
rows.go:134
db_test.go:99
Error: Received unexpected error:
there is a remaining expectation which was not matched: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
- matches sql: 'SELECT'
- is without arguments
Test: TestInit/TestFetchOrderById
All I'm trying to do is confirm that the GormDB instance was queried with the SELECT statement specified in the FetchOrderById function.
Does anybody know what I need to do to achieve this and get the test to pass?
I decided to Go (no pun intended) with Java instead.
I am building a simple function that calls an API that returns a Post using GraphQL (https://github.com/machinebox/graphql). I wrapped the logic in a service that looks like this:
type Client struct {
gcl graphqlClient
}
type graphqlClient interface {
Run(ctx context.Context, req *graphql.Request, resp interface{}) error
}
func (c *Client) GetPost(id string) (*Post, error) {
req := graphql.NewRequest(`
query($id: String!) {
getPost(id: $id) {
id
title
}
}
`)
req.Var("id", id)
var resp getPostResponse
if err := c.gcl.Run(ctx, req, &resp); err != nil {
return nil, err
}
return resp.Post, nil
}
Now I'd like to add test tables for the GetPost function with a fail case when id is set to empty string which causes an error in the downstream call c.gcl.Run.
What I am struggling with is the way the gcl client can be mocked and forced to return the error (when no real API call happens).
My test so far:
package apiClient
import (
"context"
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/google/go-cmp/cmp"
"github.com/machinebox/graphql"
"testing"
)
type graphqlClientMock struct {
graphqlClient
HasError bool
Response interface{}
}
func (g graphqlClientMock) Run(_ context.Context, _ *graphql.Request, response interface{}) error {
if g.HasError {
return errors.New("")
}
response = g.Response
return nil
}
func newTestClient(hasError bool, response interface{}) *Client {
return &Client{
gcl: graphqlClientMock{
HasError: hasError,
Response: response,
},
}
}
func TestClient_GetPost(t *testing.T) {
tt := []struct{
name string
id string
post *Post
hasError bool
response getPostResponse
}{
{
name: "empty id",
id: "",
post: nil,
hasError: true,
},
{
name: "existing post",
id: "123",
post: &Post{id: aws.String("123")},
response: getPostResponse{
Post: &Post{id: aws.String("123")},
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
client := newTestClient(tc.hasError, tc.response)
post, err := client.GetPost(tc.id)
if err != nil {
if tc.hasError == false {
t.Error("unexpected error")
}
} else {
if tc.hasError == true {
t.Error("expected error")
}
if cmp.Equal(post, &tc.post) == false {
t.Errorf("Response data do not match: %s", cmp.Diff(post, tc.post))
}
}
})
}
}
I am not sure if passing the response to the mock like this is the right way to do it. Also, I'm struggling to set the right value to the response, since an interface{} type is passed and I don't know how to convert it to the getPostResponse and set the value to Post there.
Your test cases should not go beyond the implementation. I'm specifically referring to the empty-vs-nonempty input or any kind of input really.
Let's take a look at the code you want to test:
func (c *Client) GetPost(id string) (*Post, error) {
req := graphql.NewRequest(`
query($id: String!) {
getPost(id: $id) {
id
title
}
}
`)
req.Var("id", id)
var resp getPostResponse
if err := c.gcl.Run(ctx, req, &resp); err != nil {
return nil, err
}
return resp.Post, nil
}
Nothing in the implementation above is doing anything based on the id parameter value and therefore nothing in your tests for this piece of code should really care about what input is passed in, if it is irrelevant to the implementation it should also be irrelevant to the tests.
Your GetPost has basically two code branches that are taken based on a single factor, i.e. the "nilness" of the returned err variable. This means that as far as your implementation is concerned there are only two possible outcomes, based on what err value Run returns, and therefore there should only be two test cases, a 3rd or 4th test case would be just a variation, if not an outright copy, of the first two.
Your test client is also doing some unnecessary stuff, the main one being its name, i.e. what you have there is not a mock so calling it that is not helpful. Mocks usually do a lot more than just return predefined values, they ensure that methods are called, in the expected order and with the expected arguments, etc. And actually you don't need a mock here at all so it's a good thing it isn't one.
With that in mind, here's what I would suggest you do with your test client.
type testGraphqlClient struct {
resp interface{} // non-pointer value of the desired response, or nil
err error // the error to be returned by Run, or nil
}
func (g testGraphqlClient) Run(_ context.Context, _ *graphql.Request, resp interface{}) error {
if g.err != nil {
return g.err
}
if g.resp != nil {
// use reflection to set the passed in response value
// (i haven't tested this so there may be a bug or two)
reflect.ValueOf(resp).Elem().Set(reflect.ValueOf(g.resp))
}
return nil
}
... and here are the necessary test cases, all two of them:
func TestClient_GetPost(t *testing.T) {
tests := []struct {
name string
post *Post
err error
client testGraphqlClient
}{{
name: "return error from client",
err: errors.New("bad input"),
client: testGraphqlClient{err: errors.New("bad input")},
}, {
name: "return post from client",
post: &Post{id: aws.String("123")},
client: testGraphqlClient{resp: getPostResponse{Post: &Post{id: aws.String("123")}}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := Client{gql: tt.client}
post, err := client.GetPost("whatever")
if !cmp.Equal(err, tt.err) {
t.Errorf("got error=%v want error=%v", err, tt.err)
}
if !cmp.Equal(post, tt.post) {
t.Errorf("got post=%v want post=%v", post, tt.post)
}
})
}
}
... there's a bit of repetition going on here, the need to spell out the post and err twice but that's a small price to pay when compared to a more sophisticated/complicated test setup that would populate the test client from the test case's expected output fields.
Addendum:
If you were to update GetPost in such a way that it checks for the empty id and returns an error before it sends a request to graphql then your initial setup would make much more sense:
func (c *Client) GetPost(id string) (*Post, error) {
if id == "" {
return nil, errors.New("empty id")
}
req := graphql.NewRequest(`
query($id: String!) {
getPost(id: $id) {
id
title
}
}
`)
req.Var("id", id)
var resp getPostResponse
if err := c.gcl.Run(ctx, req, &resp); err != nil {
return nil, err
}
return resp.Post, nil
}
... and updating the test cases accordingly:
func TestClient_GetPost(t *testing.T) {
tests := []struct {
name string
id string
post *Post
err error
client testGraphqlClient
}{{
name: "return empty id error",
id: "",
err: errors.New("empty id"),
client: testGraphqlClient{},
}, {
name: "return error from client",
id: "nonemptyid",
err: errors.New("bad input"),
client: testGraphqlClient{err: errors.New("bad input")},
}, {
name: "return post from client",
id: "nonemptyid",
post: &Post{id: aws.String("123")},
client: testGraphqlClient{resp: getPostResponse{Post: &Post{id: aws.String("123")}}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := Client{gql: tt.client}
post, err := client.GetPost(tt.id)
if !cmp.Equal(err, tt.err) {
t.Errorf("got error=%v want error=%v", err, tt.err)
}
if !cmp.Equal(post, tt.post) {
t.Errorf("got post=%v want post=%v", post, tt.post)
}
})
}
}
I'm using gin gonic. I've a function that extracts a token from a cookie, which actually works. I'm using this function in a route handler and want to test the handler function, but I don't know how.
Function:
// Extracts token from a cookie
func tokenFromCookie(c *gin.Context, name string) (string, error) {
token, err := c.Cookie(name)
if err != nil {
return "", err
}
return token, nil
}
Route:
func RefreshTokenHandler(accessTokenKey string, refreshTokenKey string) gin.HandlerFunc {
fn := func(c *gin.Context) {
token, err := tokenFromCookie(c, "refresh_token")
if err != nil {
_ = c.Error(err).SetMeta(noCookie)
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
})
})
Route definition:
func CreateRoutes(r *gin.Engine) *gin.Engine {
r.Use(errorHandler)
// Auth
auth := r.Group("/auth")
{
auth.GET("/refresh-token", RefreshTokenHandler(accessTokenSignatureKey, refreshTokenSignatureKey))
}
return r
}
Unit test:
func TestRefreshTokenHandler(t *testing.T) {
req, _ := http.NewRequest("GET", "/auth/refresh-token", nil)
req.AddCookie(&http.Cookie{
Name: "refresh_token",
Value: "token",
MaxAge: 604800,
Expires: time.Now().Add(time.Hour * 24 * 7),
Path: "/",
Domain: "127.0.0.1",
HttpOnly: true,
SameSite: http.SameSiteNoneMode,
Secure: secure
}
)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
got := w.Code
if gotCode != 200 {
t.Errorf("GET /auth/refresh-token; got %d, want 200", got)
}
}
The tokenFromCookie() function throws an error though:
http: named cookie not present
This is a similar unit test that I found in the gin gonic repo:
func TestContextGetCookie(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Cookie", "user=gin")
cookie, _ := c.Cookie("user")
assert.Equal(t, "gin", cookie)
_, err := c.Cookie("nokey")
assert.Error(t, err)
}
Yet I don't understand why my code doesn't work and how to re-write it.
To view the list cookies you can try this:
fmt.Println(c.Request.Cookies())
I don't understand the point of creating new function tokenFromCookie.
func RefreshTokenHandler(accessTokenKey string, refreshTokenKey string) gin.HandlerFunc {
fn := func(c *gin.Context) {
//token, err := tokenFromCookie(c, "refresh_token")
token, err := c.Cookie("refresh_token")
if err != nil {
_ = c.Error(err).SetMeta(noCookie)
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
})
})
I don't know how to test the http response given in the code below.
func getVolDetails(volName string, obj interface{}) error {
addr := os.Getenv("MAPI_ADDR")
if addr == "" {
err := errors.New("MAPI_ADDR environment variable not set")
fmt.Println(err)
return err
}
url := addr + "/path/to/somepage/" + volName
client := &http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if resp != nil {
if resp.StatusCode == 500 {
fmt.Printf("VSM %s not found\n", volName)
return err
} else if resp.StatusCode == 503 {
fmt.Println("server not reachable")
return err
}
} else {
fmt.Println("server not reachable")
return err
}
if err != nil {
fmt.Println(err)
return err
}
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(obj)
}
With the help of some references i wrote unit test for this which is given below
func TestGetVolDetails(t *testing.T) {
var (
volume v1.Volume
server *httptest.Server
)
tests := map[string]struct {
volumeName string
err error
}{
"TestOne": {"vol", nil},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
response := `{"metadata":{"annotations":{"vsm.openebs.io/targetportals":"10.98.65.136:3260","vsm.openebs.io/cluster-i ps":"10.98.65.136","openebs.io/jiva-iqn":"iqn.2016-09.com.openebs.jiva:vol","deployment.kubernetes.io/revision":"1","openebs.io/storage-pool" :"default","vsm.openebs.io/replica-count":"1","openebs.io/jiva-controller-status":"Running","openebs.io/volume-monitor":"false","openebs.io/r eplica-container-status":"Running","openebs.io/jiva-controller-cluster-ip":"10.98.65.136","openebs.io/jiva-replica-status":"Running","vsm.ope nebs.io/iqn":"iqn.2016-09.com.openebs.jiva:vol","openebs.io/capacity":"2G","openebs.io/jiva-controller-ips":"10.36.0.6","openebs.io/jiva-repl ica-ips":"10.36.0.7","vsm.openebs.io/replica-status":"Running","vsm.openebs.io/controller-status":"Running","openebs.io/controller-container- status":"Running","vsm.openebs.io/replica-ips":"10.36.0.7","openebs.io/jiva-target-portal":"10.98.65.136:3260","openebs.io/volume-type":"jiva ","openebs.io/jiva-replica-count":"1","vsm.openebs.io/volume-size":"2G","vsm.openebs.io/controller-ips":"10.36.0.6"},"creationTimestamp":null ,"labels":{},"name":"vol"},"status":{"Message":"","Phase":"Running","Reason":""}}`
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, response)
}))
os.Setenv("MAPI_ADDR", "http://"+server.URL)
if got := GetVolDetails(tt.volumeName, &volume); got != tt.err {
t.Fatalf("GetVolDetails(%v) => got %v, want %v ", tt.volumeName, got, tt.err)
}
defer server.Close()
})
}
}
Where response is the response i'm getting from the server. This gives me always different errors.
got invalid character '<' looking for beginning of value, want <nil>
got Get http://www.HugeDomains.com: net/http: request canceled (Client.Timeout exceeded while awaiting headers), want <nil>
What am I doing wrong?
Edit:
Updated the code with SOME_ADDR to MAPI_ADDR which was done while posting question. Please don't be confused with that, problem remains as it is.
You are getting a timeout but you are not specifying what timeout is set to. I suspect that this is not a time.Duration object and that is causing your timeout. There are a few other issues as well. To get this to work I did:
Change the function being called in the test to getVolDetails to match the code (not the lower case g)
Set the Timeout when creating the client to Timeout: time.Second * 10
Remove the "http://"+ from the os.Setenv("MAPI_ADDR", "http://"+server.URL) line
Corrected code is:
var timeout time.Duration = time.Second * 1000
func getVolDetails(volName string, obj interface{}) error {
addr := os.Getenv("MAPI_ADDR")
if addr == "" {
err := errors.New("MAPI_ADDR environment variable not set")
fmt.Println(err)
return err
}
url := addr + "/path/to/somepage/" + volName
client := &http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if resp != nil {
if resp.StatusCode == 500 {
fmt.Printf("VSM %s not found\n", volName)
return err
} else if resp.StatusCode == 503 {
fmt.Println("server not reachable")
return err
}
} else {
fmt.Println("server not reachable")
return err
}
if err != nil {
fmt.Println(err)
return err
}
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(obj)
}
and test:
func TestGetVolDetails(t *testing.T) {
var (
volume v1.Volume
server *httptest.Server
)
tests := map[string]struct {
volumeName string
err error
}{
"TestOne": {"vol", nil},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
response := `{"metadata":{"annotations":{"vsm.openebs.io/targetportals":"10.98.65.136:3260","vsm.openebs.io/cluster-i ps":"10.98.65.136","openebs.io/jiva-iqn":"iqn.2016-09.com.openebs.jiva:vol","deployment.kubernetes.io/revision":"1","openebs.io/storage-pool" :"default","vsm.openebs.io/replica-count":"1","openebs.io/jiva-controller-status":"Running","openebs.io/volume-monitor":"false","openebs.io/r eplica-container-status":"Running","openebs.io/jiva-controller-cluster-ip":"10.98.65.136","openebs.io/jiva-replica-status":"Running","vsm.ope nebs.io/iqn":"iqn.2016-09.com.openebs.jiva:vol","openebs.io/capacity":"2G","openebs.io/jiva-controller-ips":"10.36.0.6","openebs.io/jiva-repl ica-ips":"10.36.0.7","vsm.openebs.io/replica-status":"Running","vsm.openebs.io/controller-status":"Running","openebs.io/controller-container- status":"Running","vsm.openebs.io/replica-ips":"10.36.0.7","openebs.io/jiva-target-portal":"10.98.65.136:3260","openebs.io/volume-type":"jiva ","openebs.io/jiva-replica-count":"1","vsm.openebs.io/volume-size":"2G","vsm.openebs.io/controller-ips":"10.36.0.6"},"creationTimestamp":null ,"labels":{},"name":"vol"},"status":{"Message":"","Phase":"Running","Reason":""}}`
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, response)
}))
os.Setenv("MAPI_ADDR", server.URL)
if got := getVolDetails(tt.volumeName, &volume); got != tt.err {
t.Fatalf("GetVolDetails(%v) => got %v, want %v ", tt.volumeName, got, tt.err)
}
defer server.Close()
})
}
}