type ( // Sync struct { client HTTPClient } ) // Sync func NewSync(hc HTTPClient) *Sync { return &Sync{hc} } // func (s *Sync) Sync(user *User) error { res, err := s.client.Post(syncURL, "application/json", body) // res err return err }
type ( // Store struct { client HTTPClient } ) // Store func NewStore(hc HTTPClient) *Store { return &Store{hc} } // func (s *Store) Store(user *User) error { res, err := s.client.Get(userResource) // res err res, err = s.client.Post(usersURL, "application/json", body) // res err return err }
type ( // http HTTPClient interface { // POST- Post(url, contentType string, body io.Reader) (*http.Response, error) // GET- Get(url string) (*http.Response, error) } )
func TestUserSync(t *testing.T) { client := new(HTTPClientMock) client.PostFunc = func(url, contentType string, body io.Reader) (*http.Response, error) { // check if args are the expected return &http.Response{StatusCode: http.StatusOK}, nil } syncer := NewSync(client) u := NewUser("foo@mail.com", "de") if err := syncer.Sync(u); err != nil { t.Fatalf("failed to sync user: %v", err) } if !client.PostInvoked { t.Fatal("expected client.Post() to be invoked") } } type ( HTTPClientMock struct { PostInvoked bool PostFunc func(url, contentType string, body io.Reader) (*http.Response, error) GetInvoked bool GetFunc func(url string) (*http.Response, error) } ) func (m *HTTPClientMock) Post(url, contentType string, body io.Reader) (*http.Response, error) { m.PostInvoked = true return m.PostFunc(url, contentType, body) } func (m *HTTPClientMock) Get(url string) (*http.Response, error) { return nil, nil}
Clients should not depend on methods that they do not use. Robert MartinAlso, if you want to add a new method to the HTTPClient , you will also have to add it to the HTTPClientMock stub, which degrades the readability of the code and complicates its testing. Even if you just change the signature of the Get method, it still affects the test for the Sync structure, despite the fact that this method is not used. From such dependencies should get rid of.
type ( AMQPHandler struct { repository Repository } Repository interface { Add(user *User) error FindByID(ID string) (*User, error) FindByEmail(email string) (*User, error) FindByCountry(country string) (*User, error) FindByEmailAndCountry(country string) (*User, error) Search(...CriteriaOption) ([]*User, error) Remove(ID string) error // // // // ... } ) func NewAMQPHandler(r Repository) *AMQPHandler { return &AMQPHandler{r} } func (h *AMQPHandler) Handle(body []byte) error { // if err := h.repository.Add(user); err != nil { return err } return nil }
type ( RepositoryMock struct { AddInvoked bool } ) func (r *Repository) Add(u *User) error { r.AddInvoked = true return nil } func (r *Repository) FindByID(ID string) (*User, error) { return nil } func (r *Repository) FindByEmail(email string) (*User, error) { return nil } func (r *Repository) FindByCountry(country string) (*User, error) { return nil } func (r *Repository) FindByEmailAndCountry(email, country string) (*User, error) { return nil } func (r *Repository) Search(...CriteriaOption) ([]*User, error) { return nil, nil } func (r *Repository) Remove(ID string) error { return nil }
The larger the interface, the weaker the abstraction. Rob PikeReturning to the user management code, both the Post and Get methods are needed only to store data ( Store ), and only the Post method is enough for synchronization. Let's fix the sync implementation with this fact:
type ( // Sync struct { client HTTPPoster } ) // Sync func NewSync(hc HTTPPoster) *Sync { return &Sync{hc} } // func (s *Sync) Sync(user *User) error { res, err := s.client.Post(syncURL, "application/json", body) // res err return err }
func TestUserSync(t *testing.T) { client := new(HTTPPosterMock) client.PostFunc = func(url, contentType string, body io.Reader) (*http.Response, error) { // assert the arguments are the expected return &http.Response{StatusCode: http.StatusOK}, nil } syncer := NewSync(client) u := NewUser("foo@mail.com", "de") if err := syncer.Sync(u); err != nil { t.Fatalf("failed to sync user: %v", err) } if !client.PostInvoked { t.Fatal("expected client.Post() to be invoked") } } type ( HTTPPosterMock struct { PostInvoked bool PostFunc func(url, contentType string, body io.Reader) (*http.Response, error) } ) func (m *HTTPPosterMock) Post(url, contentType string, body io.Reader) (*http.Response, error) { m.PostInvoked = true return m.PostFunc(url, contentType, body) }
func TestUserStore(t *testing.T) { client := new(HTTPClientMock) client.PostFunc = func(url, contentType string, body io.Reader) (*http.Response, error) { // assertion omitted return &http.Response{StatusCode: http.StatusOK}, nil } client.GetFunc = func(url string) (*http.Response, error) { // assertion omitted return &http.Response{StatusCode: http.StatusOK}, nil } storer := NewStore(client) u := NewUser("foo@mail.com", "de") if err := storer.Store(u); err != nil { t.Fatalf("failed to store user: %v", err) } if !client.PostInvoked { t.Fatal("expected client.Post() to be invoked") } if !client.GetInvoked { t.Fatal("expected client.Get() to be invoked") } } type ( HTTPClientMock struct { HTTPPosterMock HTTPGetterMock } HTTPPosterMock struct { PostInvoked bool PostFunc func(url, contentType string, body io.Reader) (*http.Response, error) } HTTPGetterMock struct { GetInvoked bool GetFunc func(url string) (*http.Response, error) } ) func (m *HTTPPosterMock) Post(url, contentType string, body io.Reader) (*http.Response, error) { m.PostInvoked = true return m.PostFunc(url, contentType, body) } func (m *HTTPGetterMock) Get(url string) (*http.Response, error) { m.GetInvoked = true return m.GetFunc(url) }
type ReadWriter interface { Reader Writer }
type ( // http- HTTPClient struct { req *Request } // http- Request struct{} ) // HTTPClient func New(r *Request) *HTTPClient { return &HTTPClient{r} } // Get- func (c *HTTPClient) Get(url string) (*http.Response, error) { return c.req.Do(http.MethodGet, url, "application/json", nil) } // Post- func (c *HTTPClient) Post(url, contentType string, body io.Reader) (*http.Response, error) { return c.req.Do(http.MethodPost, url, contentType, body) } // http- func (r *Request) Do(method, url, contentType string, body io.Reader) (*http.Response, error) { req, err := http.NewRequest(method, url, body) if err != nil { return nil, fmt.Errorf("failed to create request %v: ", err) } req.Header.Set("Content-Type", contentType) return http.DefaultClient.Do(req) }
type ( // User struct { Email string `json:"email"` Country string `json:"country"` } // HTTPClient interface { HTTPGetter HTTPPoster } // Post- HTTPPoster interface { Post(url, contentType string, body io.Reader) (*http.Response, error) } // Get- HTTPGetter interface { Get(url string) (*http.Response, error) } )
func main() { req := new(httpclient.Request) client := httpclient.New(req) _ = user.NewSync(client) _ = user.NewStore(client) // Sync Store }
Source: https://habr.com/ru/post/350856/
All Articles