I have a simple service:
package services
type X struct {
Name string
}
func (x X) Valid() bool {
return x.Name != ""
}
type Service struct{}
func (service Service) Do(x X) bool {
return x.Valid()
}
And a test file that goes with it:
package services_test
import (
"playground/services"
"testing"
)
func TestService_valid(t *testing.T) {
x := services.X{
Name: "something",
}
service := services.Service{}
did := service.Do(x)
if !did {
t.Fatalf("it didn't do the thing, but should")
}
}
func TestService_invalid(t *testing.T) {
x := services.X{}
service := services.Service{}
did := service.Do(x)
if did {
t.Fatalf("it did do the thing, but shouldn't")
}
}
I’m currently setting my X
to be valid or invalid based on the knowledge of what the Valid()
function actually checks for, in the future when the validity of my X
is no longer just related to the Name
property I would have to update all my tests.
I’d much rather just mock out the Valid
method directly and have it return what I want to happen in the specific test. However, all my attempts either overcomplicate the business logic (which I don’t want to change necessarily) or require me to pass an interface to my Do
function. I don’t feel like wrapping every parameter of my service methods in interfaces just so i can mock them in my tests.
I’m probably approaching this whole problem from the wrong angle, but I can’t seem to find a clean way of doing this.
The thing I want to accomplish in my test is something like this
type mockX struct {
IsValid bool
}
func (m mockX) Valid() bool {
return m.IsValid
}
func TestService_valid(t *testing.T) {
mock := mockX{IsValid: true}
service := services.Service{}
did := service.Do(mock)
if !did {
t.Fatalf("it didn't do the thing, but should")
}
}
func TestService_invalid(t *testing.T) {
mock := mockX{IsValid: false}
service := services.Service{}
did := service.Do(mock)
if did {
t.Fatalf("it did do the thing, but shouldn't")
}
}
But the typechecker doesn’t like me passing in an xMock
where an X
is expected (which is logical)
Pass an interface instead of a struct to the Do function :
type XInterface {
Valid() bool
}
func (service Service) Do(x XInterface) bool {
return x.Valid()
}
Edit: Or add a supplementary abstraction layer in the service:
type ValidatorInterface interface {
Validate(x X) bool
}
type Service struct{
Validator ValidatorInterface
}
func (service Service) Do(x X) bool {
return service.Validator.Validate(x)
}
And use it in your tests:
type mockValidator struct {
isValid bool
}
func (m mockValidator) Validate(x X) bool {
return m.isValid
}
service := services.Service{
Validator: mockValidator{
isValid: true,
}
}
did := service.Do(X)
3