0

I write this go code for login. Now i want to unit test my code. This code is depends on controller to service layer then service to repository layer. I want to use gomock tool for mocking, if any other please suggest me. I'm using echo framework. Here

serializers.LoginReq =
{
    Email   string,
    Phone  string,
    Admin  bool 
}
type auth struct {
    authSvc svc.IAuth
    userSvc svc.IUsers
}

func NewAuthController(grp interface {}, authSvc svc.IAuth, userSvc svc.IUsers) {
    ac: = & auth {
        authSvc: authSvc,
        userSvc: userSvc,
    }
    g: = grp.( * echo.Group)
    g.POST("/v1/login", ac.Login)
}


func(ctr * auth) Login(c echo.Context) error {
    var cred * serializers.LoginReq
    var resp * serializers.LoginResp
    var err error

    if err = c.Bind( & cred) err != nil {
        return c.JSON(err.Status, err)
    }
    
    if resp, err = ctr.authSvc.Login(cred); err != nil {
        return c.JSON(err.Status, err)
          }
    return c.JSON(http.StatusOK, resp)
} 
Md. Abu Farhad
  • 373
  • 5
  • 21

1 Answers1

0

Use dependency injection. Dependency injection is a design pattern that decouples dependencies between two or more layers of software.

How it works

Pass a dependency to the Login function. In Go, the dependency is often an interface type. Interfaces express generalizations or abstractions about the behaviors of other types. A type satisfies an interface if it has all the methods in the interface. With an interface, you can replace a real object with a fake one (a mock) in your tests. This works without Go's type system complaining as long as a concrete type satisfies the interface.

type Auther interface {
  Login(cred *serializers.LoginReq) (*serializers.LoginResp, error)
}

Go Interfaces are satisfied implicitly.

// auth service must implement the Auther interface 
type auth struct {
    authSvc Auther
}

// route handler
func(ctr *auth) Login(c echo.Context) error {
    var cred * serializers.LoginReq
    var resp * serializers.LoginResp
    var err error

    if err = c.Bind( & cred) err != nil {
        return c.JSON(err.Status, err)
    }


    // the function signature of the service-level Login method must match the interface

    if resp, err = ctr.authSvc.Login(cred); err != nil {
        return c.JSON(err.Status, err)
          }
    return c.JSON(http.StatusOK, resp)
} 

I like using testify/mock library. Create a Mock.

type MockAuth struct {
  mock.Mock
}

func (m *MockAuth) Login(cred *serializers.LoginReq) (*serializers.LoginResp, error) {
    args := m.Called(cred)
    return args.Get(0).(*serializers.LoginResp), args.Error(1)
}

That's it. Just create a test.

func TestLogin (t *testing.T) {
    // setup mocks
    cred := &serializers.LoginReq{}
    mockReturn := &serializers.LoginResp{}
    mockAuth := &MockAuth{}
    // setup expectation
    mockAuth.On("Login", cred).Return(mockReturn, nil)

    // setup server
    mux := http.NewServeMux()
    mux.HandleFunc("/v1/login", func(w http.ResponseWriter, r *http.Request) {
      ec := echo.Context{}
      ctr: = &auth {
        authSvc: mockAuth
      }
      ctr.Login(ec)
    })
    
    // make request
    writer := httptest.NewRecorder()
    request, _ := http.NewRequest(http.MethodPost, "/v1/login", "password")
    mux.ServeHTTP(writer, request)
    
    // make assertions
    mockAuth.AssertExpectations(t)
    
}

The code above is not 100% correct. I don't use echo myself, however it should get you close. Hope this helps.

Ivor Scott
  • 159
  • 3
  • 12