So I wanted to isolate controllers from models in testing so that I could easily figure out stuff if troubles arise. Before, I just hit the endpoints with mock data but it was difficult to troubleshoot because the test runs from the router all the way to the datastore. So I'm thinking maybe I'll just create two versions(MockController vs Controller) for each controller(and model) and use one depending on the value of the mode variable. In a nutshell, this is how I plan to implement it.
const mode string = "test"
// UserModelInterface is the Interface for UserModel
type UserModelInterface interface {
Get()
}
// UserControllerInterface is the Interface for UserController
type UserControllerInterface interface {
Login()
}
// NewUserModel returns a new instance of user model
func NewUserModel() UserModelInterface {
if mode == "test" {
return &MockUserModel{}
} else {
return &UserModel{}
}
}
// NewUserController returns a new instance of user controller
func NewUserController(um UserModelInterface) UserControllerInterface {
if mode == "test" {
return &MockUserController{}
} else {
return &UserController{}
}
}
type (
UserController struct {um UserModelInterface}
UserModel struct {}
// Mocks
MockUserController struct {um UserModelInterface}
MockUserModel struct {}
)
func (uc *UserController) Login() {}
func (um *UserModel) Get() {}
func (uc *MockUserController) Login() {}
func (um *MockUserModel) Get() {}
func main() {
um := NewUserModel()
uc := NewUserController(um)
}
This way I could just skip sql query in the MockUserController.Login()
and only validate the payload and return a valid response.
What do you think of this design? Do you have a better implementation in mind?
I would let the code that calls NewUserController() and NewUserModel() decide whether to create a mock or real implementation. If you use that pattern of dependency injection all the way up to the top your code will become clearer and less tightly coupled. E.g. if the user controller is used by a server, it would look something like along the lines of:
Real:
u := NewUserController() s := NewServer(u)
In tests:
u := NewMockUserController() s := NewServer(u)
I would try a more slim variant spread over a models and a controllers package, like this:
// inside package controllers
type UserModel interface {
Get() // the methods you need from the user model
}
type User struct {
UserModel
}
// inside package models
type User struct {
// here the User Model
}
// inside package main
import ".....controllers"
import ".....models"
func main() {
c := &controllers.User{&models.User{}}
}
// inside main_test.go
import ".....controllers"
type MockUser struct {
}
func TestX(t *testing.T) {
c := &controllers.User{&MockUser{}}
}
For controller testing consider the ResponseRecorder of httptest package