Graph v5 SDK
After upgrading the Graph SDK to v5 the way on how to mock the service client has been changed and it doesn't get easier. There are different approaches available and a few of them are listed in this issue. My current approach of mocking is this here:
public static class RequestAdapterMockFactory
{
public static Mock<IRequestAdapter> Create(MockBehavior mockBehavior = MockBehavior.Strict)
{
var mockSerializationWriterFactory = new Mock<ISerializationWriterFactory>();
mockSerializationWriterFactory.Setup(factory => factory.GetSerializationWriter(It.IsAny<string>()))
.Returns((string _) => new JsonSerializationWriter());
var mockRequestAdapter = new Mock<IRequestAdapter>(mockBehavior);
// The first path element must have four characters to mimic v1.0 or beta
// This is especially needed to mock batch requests.
mockRequestAdapter.SetupGet(adapter => adapter.BaseUrl).Returns("http://graph.test.internal/mock");
mockRequestAdapter.SetupSet(adapter => adapter.BaseUrl = It.IsAny<string>());
mockRequestAdapter.Setup(adapter => adapter.EnableBackingStore(It.IsAny<IBackingStoreFactory>()));
mockRequestAdapter.SetupGet(adapter => adapter.SerializationWriterFactory).Returns(mockSerializationWriterFactory.Object);
return mockRequestAdapter;
}
}
var mockRequestAdapter = RequestAdapterMockFactory.Create();
var graphServiceClient = new GraphServiceClient(mockRequestAdapter.Object);
mockRequestAdapter.Setup(adapter => adapter.SendAsync(
// Needs to be correct HTTP Method of the desired method ππ»
It.Is<RequestInformation>(info => info.HttpMethod == Method.GET),
// Needs to be method from ππ» object type that will be returned from the SDK method.
Microsoft.Graph.Models.User.CreateFromDiscriminatorValue,
It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Microsoft.Graph.Models.User
{
DisplayName = "Hello World",
});
This approach is quite bloatly, but I didn't find any better for my cases. In the above linked GitHub issue you can find other approaches which maybe works better for you.
Access and use values in ReturnsAsync()
This can be achieved by using the overload of the method that takes a corresponding Func<>. In our case to e.g. access the desired id of a group request and return a group with this id, you could try the following:
mockRequestAdapter.Setup(adapter => adapter.SendAsync(
// Needs to be correct HTTP Method of the desired method ππ»
It.Is<RequestInformation>(info => info.HttpMethod == Method.GET),
// ππ» Needs to be method from object type that will be returned from the SDK method.
Group.CreateFromDiscriminatorValue,
It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((
RequestInformation info,
// Needs to be the ππ» same type as above!
ParsableFactory<Group> _,
Dictionary<string, ParsableFactory<IParsable>> _,
CancellationToken _) =>
{
var id = (string)info.PathParameters["group%2Did"];
return new Group { Id = id };
});
Whatever group you're now going to request, you'll get it back with the desired id:
var id = Guid.NewGuid().ToString();
var group = await graphServiceClient.Groups[id].GetAsync();
Graph v4 SDK
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.