Search code examples
c#.netasynchronousnunitrestsharp

Mock of RestSharp IRestClient fails with NullReferenceException even when actual implementation works fine


I have been racking my head over this for a few days and need to ask this of the community. I've developed in c# for some time, but very new to actually using async. I am making a Rest API client and decided to use RestSharp. I was expecting to use the Execute or maybe the ExecuteTaskAsync, but it seem that since I last looked at RestSharp these two methods have been deprecated so it looks like i need to make ExecuteAsync on the RestClient work.

I've spent the past several days searching stackoverflow and other references trying to sorth through this. Most of the references seem to be old and referencing the Execute or ExecuteTaskAsync several years ago, so what I have I pieced together from all those sources and numerous hours testing and rewriting.

I was able to get the following implementation to work against the real API (tweaked some variable for business purposes), but the implementation of the class works fine. It returns the expected RestResponse from which I can get the RestResponse.Content from there.

However, when I tried to set up a unit test for the method, I ran into an issue with method failing when using a mocked version of IRestClient. It keeps saying that I have a NullReferenceExcption but is unclear where exactly the issue is. For this sample I was using .netframework 4.8 and RestSharp v110.2.0.0.

Being new relatively new to async, I have tried various different ways to configure the mock Setup and Return, but I'm willing to review and test any other configurations you might have suggestions for. Any help figuring out where/why I'm getting a null reference is greatly appreciated. Thanks!

Class

using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MyLib.Classes.Samples
{
    public interface IAuthSample
    {
        string GetResponse();
        Task<RestResponse> MakeAuthApiCallAsync();
    }

    public class AuthSample : IAuthSample
    {
        public string Url;
        public string Key;
        public string Secret;
        public IRestClient Client;

        public string GetResponse()
        {
            this.Url = "http://test.com";
            this.Key = "12345";
            this.Secret = "54321";

            var response = MakeAuthApiCallAsync().GetAwaiter().GetResult();
            return response.Content;
        }


        public virtual async Task<RestResponse> MakeAuthApiCallAsync()
        {
            var options = new RestClientOptions(this.Url)
            {
                MaxTimeout = -1,
            };

            // Set up api request
            var request = new RestRequest("oauth/token", Method.Post);
            request.AddHeader("Content-Type", "application/x-www-form-urlencoded");

            request.AddParameter("grant_type", "client_credentials");
            request.AddParameter("client_id", this.Key);
            request.AddParameter("client_secret", this.Secret);

            // Set up rest client if it doesn't exist
            IRestClient client = this.Client ?? new RestClient(options);

            // Contact api and get result
            var task = await client.ExecuteAsync<RestResponse>(request, CancellationToken.None);

            return task;
        }
    }
}

Unit Test

using MyLib.Classes;
using Moq;
using RestSharp;
using System.Net;
using MyLib.Classes.Samples;

namespace MyLib.Test.Classes.Samples
{
    [TestFixture()]
    public class AuthSampleTests
    {
        [Test()]
        public void GetResponseTest()
        {
            Assert.Fail();
        }

        [Test()]
        public void TestMakeAuthApiCall_SupplyParameters_MatchResponseResults()
        {

            // Arrange

            var url = "http://test.com";
            var key = "12345";
            var secret = "54321";
            var auth = new AuthSample()
            {
                Url = url,
                Key = key,
                Secret = secret
            };
            

            var response = new RestResponse()
            {
                Content = "blah",
                StatusCode = HttpStatusCode.OK
            };

            var restClient = new Mock<IRestClient>() { CallBase = true };

            restClient.Setup(x => x.ExecuteAsync(It.IsAny<RestRequest>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(response);

            auth.Client = restClient.Object;

            // Act

            var result = auth.MakeAuthApiCallAsync().GetAwaiter().GetResult();

            // Assert

            Assert.Fail();

        }
    }
}

Exception and Stack

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=RestSharp
  StackTrace:
   at RestSharp.RestClientExtensions.<ExecuteAsync>d__10`1.MoveNext()
   at MyLib.Classes.Samples.AuthSample.<MakeAuthApiCallAsync>d__5.MoveNext() in C:\Users\bb\Documents\My Samples\VS Samples\MyApi\MyLib\Classes\Samples\AuthSample.cs:line 54

  This exception was originally thrown at this call stack:
    MyLib.Classes.Samples.AuthSample.MakeAuthApiCallAsync() in AuthSample.cs

Solution

  • Change your code to :

    // Contact api and get result
    var task = await client.ExecuteAsync(request, CancellationToken.None).ConfigureAwait(false);
    

    The "problem" with your code is that you are actually calling extension method RestClientExtensions.ExecuteAsync<T> which looks like the following:

    if (request == null) throw new ArgumentNullException(nameof(request));
    
    var response = await client.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
    return client.Serializers.Deserialize<T>(request, response, client.Options);
    

    And client.Serializers was not set up and is null.

    Another problem with your current code is that your actual method return type is RestResponse<RestResponse> (just conveniently enough RestResponse<T> inherits from RestResponse, so everything compiles and works, you just ignore T, i.e. perform extra throw-away work).