grpc / grpc-dart

The Dart language implementation of gRPC.
https://pub.dev/packages/grpc
Apache License 2.0
860 stars 271 forks source link

Best Practices Testing gRPC Services #497

Closed dwhu closed 3 years ago

dwhu commented 3 years ago

Advice on Testing gRPC Clients

grpc: ^3.0.0

Repro steps

  1. I want to create a set of tests that verify that data is being passed correctly to gRPC service. Currently the service is mocked out with Mocktail and the test looks like the following:
  class MockServiceClient extends Mock
      implements service_proto.ServiceClient {}

  class MockCreateRequest extends Mock implements service_proto.CreateRequest {}

  class MockCreateResponse extends Fake
      implements ResponseFuture<service_proto.CreateResponse> {}
  ...
   setUpAll(() {
      registerFallbackValue(MockCreateRequest());
      registerFallbackValue<ResponseFuture<service_proto.Response>>(MockCreateResponse());
    });

    setUp(() {
      serviceClient = MockServiceClient();
      authnRepository = AuthnRepository(
        serviceClient,
      );
    });
  ...
    group('signUp', () {
      setUp(() {
        when(
          () => dealerServiceClient.create(any()),
        ).thenAnswer((_) => MockCreateResponse());
      });

      test('calls create', () async {
        await authnRepository.signUp(
            givenName: givenName,
            familyName: familyName,
            email: email,
            password: password);
        verify(() =>
            dealerServiceClient.create(service_proto.CreateRequest()
              ..displayName = dealerName
              ..adminGivenName = givenName
              ..adminFamilyName = familyName
              ..adminEmailAddress = email
              ..adminPassword = password)).called(1);
      });
      ...
  });

Expected result: I would expect this test to work since I'm mocking out ResponseFuture

Actual result:

I continue to run into issues mocking or faking out ResponseFuture because it doesn't conform to the Future interface.

Error: The argument type 'Future<CreateResponse>' can't be assigned to the parameter
type 'ResponseFuture<CreateResponse>'.
 - 'Future' is from 'dart:async'.
 - 'Dealer' is from 'package:proto/service/v1/service.pb.dart' ('lib/proto/service/v1/service.pb.dart').
 - 'ResponseFuture' is from 'package:grpc/src/client/common.dart'

or even

package:test_api                                        Fake.noSuchMethod
package:async/src/delegate/future.dart 32:13            MockCreateResponse.then
dart:async                                              _awaitHelper
package:auth/repo/authn_repository.dart 79:7   AuthnRepository.signUp
test/authn_repo/repo/authn_repository_test.dart 123:31  main.<fn>.<fn>.<fn>
test/authn_repo/repo/authn_repository_test.dart 122:34  main.<fn>.<fn>.<fn>

UnimplementedError: then

Questions

  1. Is there a better way for me to write this test? Is mocking the right thing to do? Or is there a better way to fake the service?
  2. If mocking is the correct approach, how can I get grpc-dart and the mocking framework to play nicely with each-other?

If there is a recommended approach, I'm happy to contribute some docs on best practices here.

Appreciate the help & keep up the great work!

mraleph commented 3 years ago

My preference would be to just setup an actual mock backend (which is rather simple to achieve using server part of the grpc package) instead of mocking the service class itself.

enyo commented 3 years ago

@mraleph would you mind elaborating on that a bit? So you mean to actually setup a server that you then run and that responds to the requests?

mraleph commented 3 years ago

@enyo yep, exactly. if you want to test client logic then setup a mock server (with test mock services) that responds to requests from the client you want to test.