'C# mock unit test GraphServiceClient

I have a problem writing my unit test in C# using Moq and xUnit.

In my service I have the following code:

var options = new TokenCredentialOptions
{
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};

var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential);


return (await graphClient.Users.Request().Filter($"displayName eq '{mobilePhone}'").GetAsync()).FirstOrDefault();

But I don't know a method to mock the GraphClient function:

graphClient.Users.Request().Filter($"displayName eq '{mobilePhone}'").GetAsync()).FirstOrDefault();


Solution 1:[1]

Depending on your use case and existing code base you can also provide some empty stubs for both interfaces in the constructor call and use the ability to override the virtual functions. This comes in handy if you use some mock framework like Moq as provided within the documentation:

// Arrange
var mockAuthProvider = new Mock<IAuthenticationProvider>();
var mockHttpProvider = new Mock<IHttpProvider>();
var mockGraphClient = new Mock<GraphServiceClient>(mockAuthProvider.Object, mockHttpProvider.Object);

ManagedDevice md = new ManagedDevice
{
    Id = "1",
    DeviceCategory = new DeviceCategory()
    {
        Description = "Sample Description"
    }
};

// setup the call
mockGraphClient
    .Setup(g => g.DeviceManagement.ManagedDevices["1"]
        .Request()
        .GetAsync(CancellationToken.None))
        .ReturnsAsync(md)
        .Verifiable();

// Act
var graphClient = mockGraphClient.Object;
var device = await graphClient.DeviceManagement.ManagedDevices["1"]
    .Request()
    .GetAsync(CancellationToken.None);

// Assert
Assert.Equal("1",device.Id);

By using this approach you don't have to hassle around with the concrete HTTP request done on the wire. Instead you simple override (nested) method calls with their parameters and define the returned object without a serialization / deserialization step. Also be aware, that within mock you can use e.g. It.IsAny<string>() and similar constructs to define if you need an exact parameter check or something else.

Solution 2:[2]

Thank you Marcin Wojciechowski. Posting your suggestion as an answer to help other community members.

You can implement your own IHttpProvider and pass it to GraphServiceClient

IHttpProvider mock

public class MockRequestExecutingEventArgs
    {
        public HttpRequestMessage RequestMessage { get; }
        public object Result { get; set; }
 
        public MockRequestExecutingEventArgs(HttpRequestMessage message)
        {
            RequestMessage = message;
        }
    }
    public class MockHttpProvider : IHttpProvider
    {
        public ISerializer Serializer { get; } = new Serializer();
 
        public TimeSpan OverallTimeout { get; set; } = TimeSpan.FromSeconds(10);
        public Dictionary<string, object> Responses { get; set; } = new Dictionary<string, object>();
        public event EventHandler<MockRequestExecutingEventArgs> OnRequestExecuting;
        public void Dispose()
        {
        }
 
        public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
        {
            return Task.Run(() =>
            {
                string key = "GET:" + request.RequestUri.ToString();
                HttpResponseMessage response = new HttpResponseMessage();
                if(OnRequestExecuting!= null)
                {
                    MockRequestExecutingEventArgs args = new MockRequestExecutingEventArgs(request);
                    OnRequestExecuting.Invoke(this, args);
                    if(args.Result != null)
                    {
                        response.Content = new StringContent(Serializer.SerializeObject(args.Result));
                    }
                }
                if (Responses.ContainsKey(key) && response.Content == null)
                {
                    response.Content = new StringContent(Serializer.SerializeObject(Responses[key]));
                }
                return response;
            });
        }
 
        public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            return SendAsync(request);
        }
    }

Implement TestClass

[TestClass]
public class MeEndpointTests
{
    [TestMethod]
    public void MeEndpoint_GetMeInfo()
    {
        string requestUrl = "https://graph.microsoft.com/v1.0/me";
        MockHttpProvider mockHttpProvider = new MockHttpProvider();
        mockHttpProvider.Responses.Add("GET:" + requestUrl, new User()
        {
            DisplayName = "Test User"
        });
        GraphServiceClient client = new GraphServiceClient(new MockAuthenticationHelper(), mockHttpProvider);
        User response = client.Me.Request().GetAsync().Result;
 
        Assert.AreEqual("Test User", response.DisplayName);
    }
    [TestMethod]
    public void MeEndpoint_GetMeInfo_OnRequestExecuting()
    {
        string requestUrl = "https://graph.microsoft.com/v1.0/me";
        MockHttpProvider mockHttpProvider = new MockHttpProvider();
        mockHttpProvider.OnRequestExecuting += delegate (object sender, MockRequestExecutingEventArgs eventArgs)
        {
            if(eventArgs.RequestMessage.RequestUri.ToString() == requestUrl)
            {
                eventArgs.Result = new User()
                {
                    DisplayName = "Test User"
                };
            }
        };
        GraphServiceClient client = new GraphServiceClient(new MockAuthenticationHelper(), mockHttpProvider);
        User response = client.Me.Request().GetAsync().Result;
 
        Assert.AreEqual("Test User", response.DisplayName);
    }
}

You can refer to Unit Testing Graph API SDK c#, As a developer, I want to mock Graph responses to test my integration with the Microsoft Graph client SDK and Unit testing with Graph SDK

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2