diff --git a/client/mock/context.go b/client/mock/context.go new file mode 100644 index 00000000..71506ff0 --- /dev/null +++ b/client/mock/context.go @@ -0,0 +1,16 @@ +package mock + +import ( + "golang.org/x/net/context" +) + +type responseKey struct{} + +func fromContext(ctx context.Context) (map[string][]MockResponse, bool) { + r, ok := ctx.Value(responseKey{}).(map[string][]MockResponse) + return r, ok +} + +func newContext(ctx context.Context, r map[string][]MockResponse) context.Context { + return context.WithValue(ctx, responseKey{}, r) +} diff --git a/client/mock/mock.go b/client/mock/mock.go new file mode 100644 index 00000000..1cc22968 --- /dev/null +++ b/client/mock/mock.go @@ -0,0 +1,174 @@ +package mock + +import ( + "fmt" + "reflect" + "sync" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/errors" + + "golang.org/x/net/context" +) + +var ( + _ client.Client = NewClient() +) + +type MockResponse struct { + Method string + Response interface{} + Error error +} + +type MockClient struct { + Client client.Client + Opts client.Options + + sync.Mutex + Response map[string][]MockResponse +} + +func (m *MockClient) Init(opts ...client.Option) error { + m.Lock() + defer m.Unlock() + + for _, opt := range opts { + opt(&m.Opts) + } + + r, ok := fromContext(m.Opts.Context) + if !ok { + r = make(map[string][]MockResponse) + } + m.Response = r + + return nil +} + +func (m *MockClient) Options() client.Options { + return m.Opts +} + +func (m *MockClient) NewPublication(topic string, msg interface{}) client.Publication { + return m.Client.NewPublication(topic, msg) +} + +func (m *MockClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request { + return m.Client.NewRequest(service, method, req, reqOpts...) +} + +func (m *MockClient) NewProtoRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request { + return m.Client.NewProtoRequest(service, method, req, reqOpts...) +} + +func (m *MockClient) NewJsonRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request { + return m.Client.NewJsonRequest(service, method, req, reqOpts...) +} + +func (m *MockClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { + m.Lock() + defer m.Unlock() + + response, ok := m.Response[req.Service()] + if !ok { + return errors.NotFound("go.micro.client.mock", "service not found") + } + + for _, r := range response { + if r.Method != req.Method() { + continue + } + + if r.Error != nil { + return r.Error + } + + v := reflect.ValueOf(rsp) + + if t := reflect.TypeOf(rsp); t.Kind() == reflect.Ptr { + v = reflect.Indirect(v) + } + + v.Set(reflect.ValueOf(r.Response)) + + return nil + } + + return fmt.Errorf("rpc: can't find service %s", req.Method()) +} + +func (m *MockClient) CallRemote(ctx context.Context, addr string, req client.Request, rsp interface{}, opts ...client.CallOption) error { + m.Lock() + defer m.Unlock() + + response, ok := m.Response[req.Service()] + if !ok { + return errors.NotFound("go.micro.client.mock", "service not found") + } + + for _, r := range response { + if r.Method != req.Method() { + continue + } + + if r.Error != nil { + return r.Error + } + + v := reflect.ValueOf(rsp) + + if t := reflect.TypeOf(rsp); t.Kind() == reflect.Ptr { + v = reflect.Indirect(v) + } + + v.Set(reflect.ValueOf(r.Response)) + } + + return fmt.Errorf("rpc: can't find service %s", req.Method()) +} + +func (m *MockClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Streamer, error) { + m.Lock() + defer m.Unlock() + + // TODO: mock stream + return nil, nil +} + +func (m *MockClient) StreamRemote(ctx context.Context, addr string, req client.Request, opts ...client.CallOption) (client.Streamer, error) { + m.Lock() + defer m.Unlock() + + // TODO: mock stream + return nil, nil +} + +func (m *MockClient) Publish(ctx context.Context, p client.Publication, opts ...client.PublishOption) error { + return nil +} + +func (m *MockClient) String() string { + return "mock" +} + +func NewClient(opts ...client.Option) *MockClient { + options := client.Options{ + Context: context.TODO(), + } + + for _, opt := range opts { + opt(&options) + } + + r, ok := fromContext(options.Context) + if !ok { + r = make(map[string][]MockResponse) + } + + return &MockClient{ + Client: client.DefaultClient, + Opts: options, + Response: r, + } +} diff --git a/client/mock/mock_test.go b/client/mock/mock_test.go new file mode 100644 index 00000000..c57f2860 --- /dev/null +++ b/client/mock/mock_test.go @@ -0,0 +1,32 @@ +package mock + +import ( + "testing" + + "github.com/micro/go-micro/errors" + + "golang.org/x/net/context" +) + +func TestClient(t *testing.T) { + response := []MockResponse{ + {Method: "Foo.Bar", Response: map[string]interface{}{"foo": "bar"}}, + {Method: "Foo.Fail", Error: errors.InternalServerError("go.mock", "failed")}, + } + + c := NewClient(Response("go.mock", response)) + + for _, r := range response { + req := c.NewJsonRequest("go.mock", r.Method, map[string]interface{}{"foo": "bar"}) + var rsp map[string]interface{} + + err := c.Call(context.TODO(), req, &rsp) + + if err != r.Error { + t.Fatalf("Expecter error %v got %v", r.Error, err) + } + + t.Log(rsp) + } + +} diff --git a/client/mock/options.go b/client/mock/options.go new file mode 100644 index 00000000..9011cd16 --- /dev/null +++ b/client/mock/options.go @@ -0,0 +1,17 @@ +package mock + +import ( + "github.com/micro/go-micro/client" +) + +// Response sets the response methods for a service +func Response(service string, response []MockResponse) client.Option { + return func(o *client.Options) { + r, ok := fromContext(o.Context) + if !ok { + r = make(map[string][]MockResponse) + } + r[service] = response + o.Context = newContext(o.Context, r) + } +}