Unit testing Kiota API clients
Unit testing API clients against the actual API can often be impractical. Issues like network failures, authentication requirements, or changing data can make testing difficult or impossible. We recommend that unit tests use mock implementations of the HTTP transport layer to control API responses.
For Kiota API clients, the HTTP layer is contained in a request adapter. Mocking the request adapter allows you to control API responses.
In this sample, the following frameworks/tools are used.
- xUnit - unit testing framework for .NET
- NSubstitute - mocking library to mock interfaces
The following unit tests are for an API client generated for the /posts
endpoint in the JSONPlaceholder REST API. The full project can be downloaded from GitHub.
using System.Text.Json;
using KiotaPosts.Client;
using KiotaPosts.Client.Models;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Serialization.Json;
using NSubstitute;
namespace KiotaPostsTests;
public class PostsRequestsTests
{
private static readonly List<Post> postsMock = new()
{
new Post
{
Id = 1,
UserId = 1,
Title = "Post Title #1",
Body = "Posts Body #1",
},
new Post
{
Id = 2,
UserId = 2,
Title = "Post Title #2",
Body = "Post Body #2",
},
};
// Basic test for GET /posts
[Fact]
public async Task All_Posts()
{
// Arrange
var adapter = Substitute.For<IRequestAdapter>();
var postsClient = new PostsClient(adapter);
// Return the mocked list of posts
adapter.SendCollectionAsync(
Arg.Any<RequestInformation>(),
Arg.Any<ParsableFactory<Post>>(),
Arg.Any<Dictionary<string, ParsableFactory<IParsable>>>(),
Arg.Any<CancellationToken>())
.ReturnsForAnyArgs(postsMock);
// Act
var posts = await postsClient.Posts.GetAsync();
// Assert
Assert.NotNull(posts);
Assert.Equal(postsMock.Count, posts.Count);
}
// Basic test for GET /posts/{id}
[Fact]
public async Task Post_By_Id()
{
// Arrange
var adapter = Substitute.For<IRequestAdapter>();
var postsClient = new PostsClient(adapter);
// Return the mocked post #1 object if the path
// contains the ID of post #1
adapter.SendAsync(
Arg.Is<RequestInformation>(req => req.PathParameters.Values.Contains(1)),
Arg.Any<ParsableFactory<Post>>(),
Arg.Any<Dictionary<string, ParsableFactory<IParsable>>>(),
Arg.Any<CancellationToken>())
.Returns(postsMock[0]);
// Return the mocked post #2 object if the path
// contains the ID of post #2
adapter.SendAsync(
Arg.Is<RequestInformation>(req => req.PathParameters.Values.Contains(2)),
Arg.Any<ParsableFactory<Post>>(),
Arg.Any<Dictionary<string, ParsableFactory<IParsable>>>(),
Arg.Any<CancellationToken>())
.Returns(postsMock[1]);
// Act
var post1 = await postsClient.Posts[1].GetAsync();
var post2 = await postsClient.Posts[2].GetAsync();
// Assert
Assert.NotNull(post1);
Assert.Equal(postsMock[0].Title, post1.Title);
Assert.NotNull(post2);
Assert.Equal(postsMock[1].Title, post2.Title);
}
// Basic test for POST /posts
[Fact]
public async Task Post_Create_New()
{
// Arrange
var adapter = Substitute.For<IRequestAdapter>();
var postsClient = new PostsClient(adapter);
// Add JsonSerializationWriter to serialize Post objects
adapter.SerializationWriterFactory.GetSerializationWriter("application/json")
.Returns(sw => new JsonSerializationWriter());
// When the request is sent through the adapter
// save it so we can validate the content of the request
RequestInformation? postRequest = null;
await adapter.SendAsync(
Arg.Do<RequestInformation>(req => postRequest = req),
Arg.Any<ParsableFactory<Post>>(),
Arg.Any<Dictionary<string, ParsableFactory<IParsable>>?>(),
Arg.Any<CancellationToken>());
var newPost = new Post
{
UserId = 3,
Title = "Created new post",
Body = "For testing purposes",
};
// Act
await postsClient.Posts.PostAsync(newPost);
// Assert
Assert.NotNull(postRequest);
// Deserialize the request body
var postFromBody = JsonSerializer.Deserialize<Post>(
postRequest.Content,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
Assert.NotNull(postFromBody);
Assert.Equal(newPost.UserId, postFromBody.UserId);
Assert.Equal(newPost.Title, postFromBody.Title);
Assert.Equal(newPost.Body, postFromBody.Body);
}
}