0

I am using flurl and I am trying to unit test the code below:

public class MyRestClient
{
    public async Task<T> Request<T>(IFlurlRequest flurlRequest)
    {
        try  
        {
            return await flurlRequest
                    .WithOAuthBearerToken("my-hardcoded-token")
                    .GetAsync()
                    .ReceiveJson<T>();  
        }
        catch(HttpFlurlException)
        {
            throw new MyCustomException();
        }
    }
}

What I want to test is that if flurlRequest throws an exception of type HttpFlurlException then it will throw MyCustomException. My idea is to moq the flurlrequest and throw an exception. This is how I layed out my test:

var moq = Substitute.For<IFlurlRequest>();
// Problem is with line below:
moq.When(x => x.WithOAuthBearerToken("dummy")).Do(x => { throw new HttpFlurlException(); } );

var myClient = new MyRestClient();

Func<Task> call = async () => { await myClient.Request<object>(moq); };

// FluentAssertions
call.Should().Throw<MyCustomException>();

The code when ran returns a NullReferenceException:

Exception has occurred: CLR/System.NullReferenceException
An exception of type 'System.NullReferenceException' occurred in 
Flurl.Http.dll but was not handled in user code: 'Object reference not 
set to an instance of an object.'
at Flurl.Http.HeaderExtensions.WithHeader[T](T clientOrRequest, String name, Object value)

So I see its something related to headers... so I tried also mocking that by adding:

var moq = Substitute.For<IFlurlRequest>();
moq.Headers.Returns(new Dictionary<string, object> { {"dummy", new {} };

But I'm constantly getting the same exception. What am I doing wrong?

boywonder
  • 116
  • 2
  • 11

1 Answers1

2

WithOAuthBearerToken is an extension method, which means it cannot be mocked directly by NSubstitute. When you call When..Do or Returns on an extension method it will run the real code of the extension method. (I recommend adding NSubstitute.Analyzers to your test project to detect these cases.)

Tracing through the extension method implementation at the time of writing, it should be possible to mock the Headers property to throw the required exception, but I think this is dragging in much too much internal knowledge of the library and will result in brittle tests that are tightly coupled to that specific implementation (which is what we are aiming to avoid with mocking!).

I would be very wary of mocking out a third part library in this way, as I outlined in this answer:

The other option is to test this at a different level. I think the friction in testing the current code is that we are trying to substitute for details of [a third-party library], rather than interfaces we've created for partitioning the logical details of our app. Search for "don't mock types you don't own" for more information on why this can be a problem (I've written about it before here).

If possible I suggest trying to use Flurl's built-in testing support instead. That should enable you to fake out the behaviour you need without requiring specific details about Flurl's internal implementation.

David Tchepak
  • 9,826
  • 2
  • 56
  • 68
  • That's brilliant and I did wonder - if I'm having so much trouble mocking this, is this a warning sign in itself, and it was. The extension method makes complete sense, and I will certainly setup the NSub analyser! Thanks, you've been extremely helpful! – boywonder Nov 30 '18 at 06:41