wire
wire copied to clipboard
What about flexible mocking?
Let's imagine, we have an application with a lot of business logic and big amount of abstraction layers. This application could make calls to external (for example http) services. When it comes to functional testing, we want to mock our caller service and control response it will return to us . The rest of application should work in its normal mode.
When we have some kind of IoC container, we could instantiate our application and then replace needed services in container with corresponding mock. Thanks to separate definitions, we don't need to manually call the whole dependency graph, we just need to get the container with default definitions for each services and then replace needed with mocks.
Is there a way in wire to swap one dependency in the middle of dependency graph?
I'm not sure I understand the question, but have you looked through the guide? Generated injectors are just plain functions and you can share initialization rules between injectors with provider sets.
First of all - yes, i read all the info from this repo and all the forums and guides i found. I'm trying to better understand, how to write functional tests with this library. How to easy mock some of the dependencies. For example i have some kind of application, which is maximally layered due improving testability:
package main
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
)
type User struct {
Id int
Roles []Role
}
type Role struct {
Id int
}
type UserRepository interface {
GetById(id int) (*User, error)
}
type RoleRepository interface {
GetByUserId(id int) ([]Role, error)
}
type UserFetcher interface {
FetchWithRolesById(id int) (*User, error)
}
type Script interface {
Run() error
}
type QueueSender interface {
Send(msg []byte) error
}
type PermissionChecker interface {
Check(permission string) error
}
type permissionChecker struct {
client http.Client
}
func (ch permissionChecker) Check(permission string) error {
// perform request for permission microservice over http
return nil
}
func NewPermissionChecker(client http.Client) *permissionChecker {
return &permissionChecker{client: client}
}
type rmqSender struct {
// some rmqConnection
}
func NewRmqSender() *rmqSender {
return &rmqSender{}
}
func (rmqSender) Send(msg []byte) error {
fmt.Println(fmt.Sprintf("sent: %s", msg))
return nil
}
type userFetcher struct {
userRepository UserRepository
roleRepository RoleRepository
}
func NewUserFetcher(userRepository UserRepository, roleRepository RoleRepository) *userFetcher {
return &userFetcher{userRepository: userRepository, roleRepository: roleRepository}
}
func (f userFetcher) FetchWithRolesById(id int) (*User, error) {
return &User{Id: id, Roles: []Role{}}, nil
}
type userRepository struct {
conn *sql.DB
}
func (r userRepository) GetById(id int) (*User, error) {
return &User{Id: id}, nil
}
func NewUserRepository(conn *sql.DB) *userRepository {
return &userRepository{conn: conn}
}
type roleRepository struct {
conn *sql.DB
}
func (r roleRepository) GetByUserId(id int) ([]Role, error) {
return []Role{}, nil
}
func NewRoleRepository(conn *sql.DB) *roleRepository {
return &roleRepository{conn: conn}
}
type appScript struct {
userFetcher UserFetcher
queueSender QueueSender
permissionChecker PermissionChecker
}
func NewAppScript(fetcher UserFetcher, queueSender QueueSender, permissionChecker PermissionChecker) *appScript {
return &appScript{userFetcher: fetcher, queueSender: queueSender, permissionChecker: permissionChecker}
}
func (s appScript) Run() error {
if err := s.permissionChecker.Check("get user"); err != nil {
return err
}
user, err := s.userFetcher.FetchWithRolesById(1)
if err != nil {
return err
}
serializedUser, err := json.Marshal(user)
if err != nil {
return err
}
return s.queueSender.Send(serializedUser)
}
type handler struct {
appScript Script
}
func NewHandler(appScript Script) *handler {
return &handler{appScript: appScript}
}
func (h handler) Handle(w http.ResponseWriter, r *http.Request) {
if err := h.appScript.Run(); err == nil {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("got error: %s", err)))
}
}
func main() {
dbConn := &sql.DB{}
userRepo := NewUserRepository(dbConn)
roleRepo := NewRoleRepository(dbConn)
fetcher := NewUserFetcher(userRepo, roleRepo)
permChecker := NewPermissionChecker(http.Client{})
queueSender := NewRmqSender()
script := NewAppScript(fetcher, queueSender, permChecker)
handler := NewHandler(script)
r := http.NewServeMux()
r.HandleFunc("/", handler.Handle)
if err := http.ListenAndServe("localhost:9998", r); err != nil {
fmt.Println(fmt.Sprintf("error: %s", err))
}
}
As you can see there are some external dependencies, such as Database, Queue engine connection and external http request. I want to test that endpoint, but also i want to mock permissionChecker in one test and Queue sender in another test. What i don't want - to initialize the full handler like in the main function, replacing services i want to control with mocks. Because if my handler has bigger dependency graph depth, it will take much bigger amount of code to write each test with mocks.
I found google wire lib, when i was searching for DI container, the approach Java spring is using. I also found out, that golang community reject the idea of DI container, because it is not "the GO way". But it is still don't clear for me, how google wire could fundamentally solve the problem i get. As i see, it makes everything the same as i do, but gives some code generation on top of that.
Sorry for Big comment, but i really need some more explanation. Thank you!
Hello @zombiezen , did you managed how to write test with wire?
Has the problem been solved?