2

I've been following the guidelines here - https://docs.servicestack.net/testing

I'm trying to do unit testing rather than integration, just to cut down the level of mocking and other complexities.

Some of my services call some of my other services, via the recommended IServiceGateway API, e.g. Gateway.Send(MyRequest).

However when running tests i'm getting System.NotImplementedException: 'Unable to resolve service 'GetMyContentRequest''.

I've used container.RegisterAutoWired() which is the service that handles this request.

I'm not sure where to go next. I really don't want to have to start again setting up an integration test pattern.

richardwhatever
  • 4,564
  • 5
  • 23
  • 26
  • How did you solve this eventually? I've got the exact same challenge. It works fine if I don't use gateway, just Resolve the "other service" from the calling one, however that's not the recommended way it seems. – specimen Sep 11 '21 at 18:02

2 Answers2

3

You're likely going to continually run into issues if you try to execute Service Integrations as unit tests instead of Integration tests which would start in a verified valid state.

But for Gateway Requests, they're executed using an IServiceGateway which you can choose to override by implementing GetServiceGateway() in your custom AppHost with a custom implementation, or by registering an IServiceGatewayFactory or IServiceGateway in your IOC, here's the default implementation:

public virtual IServiceGateway GetServiceGateway(IRequest req)
{
    if (req == null)
        throw new ArgumentNullException(nameof(req));

    var factory = Container.TryResolve<IServiceGatewayFactory>();
    return factory != null ? factory.GetServiceGateway(req) 
        : Container.TryResolve<IServiceGateway>()
        ?? new InProcessServiceGateway(req);
}
mythz
  • 141,670
  • 29
  • 246
  • 390
  • Yes, i had just realised this could lead to ongoing spiral of calls. – richardwhatever May 01 '20 at 07:02
  • Is there no simpler way? AppHost-based integration test are slow, so I prefer BasicAppHost based "unit test". Inside test I'd do `var srv = container.Resolve(); srv.Get(new MyDto(...))`. _MyService_ calls an (internal) service _OtherService_. In the test OtherService is _mocked_ and added to _BasicAppHost's_ container. Inside `MyService.Get(MyDto ..)` using `Resolve` works fine, but is not recommended. SS docs recommends `Gateway.Send`, which doesn't work (can't find service). – specimen Sep 11 '21 at 18:06
  • @specimen if you just want to test your service impl you can just resolve it from the IOC and call the method directly however this doesn’t test any of the AppHost integration and will fail if your Service tries to access the IRequest or IResponse otherwise it should be ok. However I’d always recommend integration tests which is a better test of the real environment. If it’s slow have fewer but larger test fixtures so the AppHost startup cost is only done once per fixture. – mythz Sep 11 '21 at 18:09
  • Integration test (AppHost+JsonServiceClient) certainly works, and perhaps I'll need to use that for every test. It seems as soon as I introduce the ServiceGateway inside a service, all existing unit tests of that service must be rewritten as integration tests. Another finding is that if ResolveService (inside service) returns _null_ when used from an integration test, but works from unit test (when service is manually added to container). With gateway it's the other way around. This leads me to believe that mixing Unit Tests and Integration Tests leads to problems. – specimen Sep 11 '21 at 18:35
  • @specimen Right everything that accesses the Request/Response like the Gateway needs a real API request. It sounds your AppHost isn’t registering the required services, either way I’d recommend integration tests unless you want to test individual Services in isolation like a normal mocked dependency – mythz Sep 11 '21 at 18:46
  • @mythz yes I'd like to test just the service, with mocked dependencies for the sub-services, just can't figure out how. Example: ```public object Any(GetCustomerOrders request) { return new GetCustomerOrders { Customer = Gateway.Send(new GetCustomer { Id = request.Id }), Orders = Gateway.Send(new QueryOrders { CustomerId = request.Id }) }; }``` where GetCustomer and/or QueryOrders are mocks? What do I do? Integration test w. mocks? – specimen Sep 11 '21 at 19:38
  • 1
    @specimen if your Service uses a Service Gateway you’d need to use an integration test or mock IServiceGateway in the IOC – mythz Sep 11 '21 at 19:49
  • 1
    Thanks for your patience. What I did wrong was to mock the service, when I should have been mocking the gateway. I have taken the time to post a complete answer, _for future generations_. – specimen Sep 11 '21 at 21:11
1

Based on the discussion in the answer by @mythz, this is my solution:

Use case like OP: Test the "main" service, and mock the "sub service", and like OP I'd want to do that with Unit test (so BasicAppHost), because it's quicker, and I believe it is easier to mock services that way (side note: for AppHost based integration test, SS will scan assemblies for (real) Services, so how to mock? Unregister from "container" and replace w. mock?).

Anyway, for the unit test:

My "main" service is using another service, via the IServiceGateway (which is the officially recommended way):

public MainDtoResponse Get(MainDto request) {
  // do some stuff
  var subResponse = Gateway.Send(new SubDto { /* params */ });
  // do some stuff with subResponse
}

In my test setup:

appHost = new BasicAppHost().Init();

var mockGateway = new Mock<IServiceGateway>(); // using Moq
mockGateway.Setup(x => x.Send<SubDtoResponse>(It.IsAny<SubDto>()))
  .Returns(new SubDtoResponse { /* ... */ });
container.Register(mockGateway.Object);

So the IServiceGateway must be mocked, and then the Send method is the important one. What I was doing wrong was to mock the service, when I should have mocked the Gateway.

Then call the main service (under test) in the normal fashion for a Unit Test, like in the docs:

var s = appHost.Container.Resolve<MainService>(); // must be populated in DI manually earlier in code
s.Get(new MainDto { /* ... */ })

PS: The mockGateway.Setup can be used inside each test, not necessarily in the OneTimeSetUp.

specimen
  • 1,735
  • 14
  • 23