Unit testing Rx Catch - c#

I'm unit testing the following service and the issue is that I'm able to test only the happy path but not the unhappy path. So how do I test them? TestScheduler? http://introtorx.com/Content/v1.0.10621.0/16_TestingRx.html
These lines are left untested:
.Catch<string, TimeoutExceptiocn>(_ => Observable.Return("Timeout"))
.Catch<string, Exception>(ex => Observable.Return(ex.Message))
public interface IProductService
{
Task<string> GetAsync();
}
public sealed class ProductService : IProductService
{
private readonly HttpClient _httpClient;
public ProductService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public Task<string> GetAsync()
{
return Observable
.FromAsync(() => _httpClient.GetAsync("http://www.mocky.io/v2/5e307edf3200005d00858b49"))
.SubscribeOn(TaskPoolScheduler.Default)
.Retry(5)
.Timeout(TimeSpan.FromSeconds(5))
.Do(x => Console.WriteLine($"Is message successful? {x.IsSuccessStatusCode}"))
.SelectMany(async responseMessage =>
{
var response = await responseMessage.Content.ReadAsStringAsync();
return response;
})
.Catch<string, TimeoutException>(_ => Observable.Return("Timeout"))
.Catch<string, Exception>(ex => Observable.Return(ex.Message))
.ToTask();
}
}
public class ProductServiceTests
{
[Fact]
public async Task GetAsync_ShouldReturnText_WhenRequestIsSent()
{
// Arrange
var messageHandler = new MockHttpMessageHandler("Stuff I want to return", HttpStatusCode.OK);
var httpClient = new HttpClient(messageHandler);
var sut = new ProductService(httpClient);
// Act
var result = await sut.GetAsync();
// Assert
result.Should().Be("Stuff I want to return");
}
// TODO: Unhappy path
}

Related

How can I return Task<bool> from a Mock method using Xunit and Moq

I have this Repository class
public class ComplaintRepository : IComplaintRepository
{
private readonly IAppDbContext _context;
public ComplaintRepository(IAppDbContext context)
{
_context = context;
}
public async Task<bool> AddAsync(Complaint complaint, CancellationToken token)
{
await Task.Run(() => _context.Complaints.AddAsync(complaint));
return await SaveAsync(token);
}
private async Task<bool> SaveAsync(CancellationToken token)
{
if (await _context.SaveChangesAsync(token) > 0)
return true;
else
return false;
}
I want to set up a test for the class using xUnit and Moq. When I mock the behavior of the AddAsync method like so:
public class ComplaintRepositoryTests
{
[Fact]
public async void AddAsync_WithValidComplaint_ShouldReturnTrue()
{
// Arrange
var mockContext = new Mock<IAppDbContext>();
mockContext.Setup(x => x.Complaints.AddAsync(It.IsAny<Complaint>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
mockContext.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(1);
var complaintRepository = new ComplaintRepository(mockContext.Object);
var complaint = new Complaint();
// Act
var result = await complaintRepository.AddAsync(complaint, CancellationToken.None);
// Assert
Assert.True(result);
mockContext.Verify(x => x.Complaints.AddAsync(It.IsAny<Complaint>(), It.IsAny<CancellationToken>()), Times.Once());
mockContext.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once());
}
}
I am getting an error for the return type of the AddAsync method.
this is the error I am getting:
cannot convert from 'System.Threading.Tasks.Task<bool>' to 'System.Threading.Tasks.ValueTask<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<CCMP.Domain.Complaints.Complaint>>'
I don't know how to go about it. I have tried all the things I have seen online to no avail.

Faking GetAsync call from test

public Fixture()
{
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices(services =>
{
services.AddScoped<ICarService, CarService>();
}));
Client = _server.CreateClient();
}
from tests I'm using this HttpClient to test my API.
using (var response = await _client.GetAsync($"/api/car/{id}"))
{
//...
}
The thing is that I want to fake the result of the GetAsync(int id) method in CarService class.
So I tried
var myCarObject = ... omitted for clarity
var myCarMockService = new Mock<ICarService>();
myCarMockService.Setup(x => x.GetAsync(It.IsAny<int>())).Returns(Task.FromResult(myCarObject));
I don't know is this right approach, but if it is how can I inject it
into Fixture class so CarService can use it.
public class CarService: ICarService {
private readonly CarDbContext _carDbContext;
public CarService(CarDbContext carDbContext)
{
_carDbContext = carDbContext;
}
public async Task<Car> GetAsync(int id)
{
return await _carDbContext.Cars.FindAsync(id);
}
}
Update:
private readonly ICarService _carService;
public CarController(ICarService carService)
{
_carService = carService;
}
public async Task<IActionResult> Get([FromRoute] int id)
{
var car = await _carService.GetAsync(id);
}
Update 2:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CarDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("Db")); });
services.AddTransient<ICarService, CarService>();
}
}
public class CarService : ICarService
{
private readonly CarDbContext _carDbContext;
public ContactService(CarDbContext carDbContext)
{
_carDbContext= carDbContext;
}
public async Task<Owner> GetAsync(int ownerId)
{
var owner = await _carDbContext.Owners.FindAsync(ownerId);
return owner.Car;
}
}
Update 3:
private readonly TestServer _server;
public Fixture()
{
var dbContextOptions = new DbContextOptionsBuilder<CarDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var mockContext = new Mock<CarDbContext>(dbContextOptions);
var mockOwnerSet = new Mock<DbSet<Owner>>();
var mockCarSet = new Mock<DbSet<Car>>();
mockContext.Setup(m => m.Owners).Returns(mockOwnerSet.Object);
mockContext.Setup(m => m.Cars).Returns(mockCarSet.Object);
var carService = new CarService(mockContext.Object);
_server = new TestServer(new WebHostBuilder()
.ConfigureAppConfiguration((context, conf) =>
{
conf.AddJsonFile(#Directory.GetCurrentDirectory() + "../appsettings.json");
}).UseStartup<Startup>()
.ConfigureServices(services =>
{
services.AddDbContext<CarDbContext>(options => options.UseInMemoryDatabase("Test"));
services.AddScoped<ICarService>(_ => carService);
})
);
Client = _server.CreateClient();
}
Configure the test server to use the mocked service
public Fixture() {
Car myCarObject = //... omitted for brevity
var myCarMockService = new Mock<ICarService>();
myCarMockService
.Setup(x => x.GetAsync(It.IsAny<int>()))
.ReturnsAsync(myCarObject);
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices(services => {
var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(ICarService));
if (serviceDescriptor != null) services.Remove(serviceDescriptor);
services.AddTransient<ICarService>(_ => myCarMockService.Object); // <-- NOTE
})
);
Client = _server.CreateClient();
}
That way when the call is made the mocked service will be injected as expected.

XUnit mocking a method but doesn't return expected result

I am using moq in XUnit test but for some reason the mock is not working properly. Here's my unit test:
[Fact]
public async Task SampleUnitTest()
{
//Arrange
var httpClient = new HttpClient(new FakeHttpMessageHandler());
_mockConstructRequest.Setup(x => x.ConstructRequestString(searchRequestModel))
.Returns("a sample string");
var service = new LibraryService(_mockConstructRequest.Object);
//Act
var response = service.GetResponse(new Request(), httpClient);
//Assert
response.Should().BeNull();
}
private class FakeHttpMessageHandler : HttpMessageHandler
{
public Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> HttpRequestHandler { get; set; } =
(r, c) =>
new HttpResponseMessage
{
ReasonPhrase = r.RequestUri.AbsoluteUri,
StatusCode = HttpStatusCode.OK
};
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(HttpRequestHandler(request, cancellationToken));
}
}
And here is the actual code and I am trying to test GetResponse method.
public class LibraryService : ILibraryService
{
private IConfiguration _configuration;
private IRequestConstructor _requestContructor;
public LibraryService (IRequestConstructor requestConstructor)
{
_requestConstructor = requestConstructor;
}
public async Task<Response> GetResponse(Request request, HttpClient client)
{
//construct request
string requestString = _constructRequestString.ConstructRequestString(request, client);
return null;
}
}
public class RequestContructor : IRequestContructor
{
public string ConstructRequestString(Request request)
{
return "a request string";
}
}
I was trying to step through the code from my unit test but when the break point is at this line, requestString variable is null while it is supposed to return "a sample string". Anyone knows why?
string requestString = _constructRequestString.ConstructRequestString(request, client);
As far as I can see you are mocking incorrectly:
Your mock:
_mockConstructRequest.Setup(x => x.ConstructRequestString(searchRequestModel))
.Returns("a sample string");
Method you are calling:
_constructRequestString.ConstructRequestString(request, client);
Should not it be something like this:
_mockConstructRequest.Setup(x => x.ConstructRequestString(It.IsAny<Request>(),It.IsAny<HttpClient>()))
.Returns("a sample string");
On top of that:
Try to initialize your mocks and your "classUnderTest" in constructor instead of in each test, it will run each time before the test and will do everything for you. For example:
public class UnitTestClass{
private readonly ClassUnderTest _classUnderTest;
private readonly Mock<ClassUnderTestDependecy> mockedInstance;
public UnitTestClass {
mockedInstance= new Mock<ClassUnderTestDependecy>();
_classUnderTest= new ClassUnderTest (ClassUnderTestDependecy.Object);
}
}

Testing controller with xUnit

I have webapi where it needs to call some other endpoint and get data.
My current code as follows
//http client implementation
public interface IHttpClientFactory
{
HttpClient Create();
}
public class HttpClientFactory : IHttpClientFactory
{
private readonly ApplicationSettings _applicationSettings;
HttpClient _httpClient;
public HttpClientFactory(IOptions<ApplicationSettings> settings)
{
_applicationSettings = settings.Value;
}
public HttpClient Create()
{
if (_httpClient != null)
return _httpClient;
var client = new HttpClient()
{
BaseAddress = new Uri($"{_applicationSettings.BaseUrl}")
};
_httpClient = client;
return _httpClient;
}
}
public interface IGetItemsQuery
{
Task<IEnumerable<T>> Execute<T>(string url);
}
public class GetItemQuery: IGetItemsQuery
{
private readonly IHttpClientFactory _httpClientFactory;
public GetPhotosQuery(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<IEnumerable<T>> Execute<T>(string url)
{
using (var response = await _httpClientFactory.Create().GetAsync($"{url}").ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
var resp = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var items = JArray.Parse(resp);
return items.ToObject<T[]>();
}
}
In my controller part
private readonly IGetItemsQuery _getItemsQuery;
public HomeController(IGetItemsQuery getItemsQuery)
{
_getItemsQuery = getItemsQuery;
}
appsettings
"ApplicationSettings": {
"BaseUrl": "http://someendpoint.com/"
}
Startup
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
services.AddScoped<IGetItemsQuery, GetPhotosQuery>();
services.AddScoped<IHttpClientFactory, HttpClientFactory>();
I want to try something like below in my test
[Fact]
public void Test_Index()
{
// Arrange
var itemsQuery = new Mock<IGetItemsQuery>();
var controller = new HomeController(itemsQuery.Object);
// Act
var result = controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewName);
}
This is creating mock IGetItemsQuery but this isn't mocking the actual IHttpClientFactory.
Is there a way to do this
Based on your design with the abstracted dependencies there would be no need to mock a client factory in order to unit test the controller.
As you have done in your test, you mock IGetItemsQuery, but you have not set it up to behave as expected when invoked in the test.
If, for example, the controller method under test look something like this
private readonly IGetItemsQuery getItemsQuery;
public HomeController(IGetItemsQuery getItemsQuery) {
this.getItemsQuery = getItemsQuery;
}
public async Task<IActionResult> Index() {
var url = "....";
var items = await getItemsQuery.Execute<MyItem>(url);
return View(items);
}
Then an isolated unit test for the Index action as the method under test could look something like
[Fact]
public async Task Index_Should_Return_View_With_Items() {
// Arrange
var itemsQuery = new Mock<IGetItemsQuery>();
var items = new MyItem[] {
new MyItem(),
new MyItem()
};
itemsQuery.Setup(_ => _.Execute<MyItem>(It.IsAny<string>()))
.ReturnsAsync(items);
var controller = new HomeController(itemsQuery.Object);
// Act
var result = await controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewName);
}

How to mock Microsoft Graph API SDK Client?

I have used Microsoft Graph SDK in my project to call graph API, for this I need to use GraphServiceClient.
To use GraphServiceClient, i have to add some helper classes, in which SDKHelper is a static class which has GetAuthenticatedClient() method.
Since method under test is tightly coupled to SDKHelper which is static, so I have created a service class and injected the dependency.
Below is the controller and method,
public class MyController
{
private IMyServices _iMyServices { get; set; }
public UserController(IMyServices iMyServices)
{
_iMyServices = iMyServices;
}
public async Task<HttpResponseMessage> GetGroupMembers([FromUri]string groupID)
{
GraphServiceClient graphClient = _iMyServices.GetAuthenticatedClient();
IGroupMembersCollectionWithReferencesPage groupMembers = await _iMyServices.GetGroupMembersCollectionWithReferencePage(graphClient, groupID);
return this.Request.CreateResponse(HttpStatusCode.OK, groupMembers, "application/json");
}
}
Service Class,
public class MyServices : IMyServices
{
public GraphServiceClient GetAuthenticatedClient()
{
GraphServiceClient graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await SampleAuthProvider.Instance.GetAccessTokenAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
}));
return graphClient;
}
public async Task<IGraphServiceGroupsCollectionPage> GetGraphServiceGroupCollectionPage(GraphServiceClient graphClient)
{
return await graphClient.Groups.Request().GetAsync();
}
}
I am having challenge in writing Unit Test Case for the above service class methods, Below is my Unit Test Code:
public async Task GetGroupMembersCollectionWithReferencePage_Success()
{
GraphServiceClient graphClient = GetAuthenticatedClient();
IGraphServiceGroupsCollectionPage groupMembers = await graphClient.Groups.Request().GetAsync();
Mock<IUserServices> mockIUserService = new Mock<IUserServices>();
IGraphServiceGroupsCollectionPage expectedResult = await mockIUserService.Object.GetGraphServiceGroupCollectionPage(graphClient);
Assert.AreEqual(expectedResult, groupMembers);
}
In Above Test case line number 4 throws an exception -
Message: The type initializer for 'Connect3W.UserMgt.Api.Helpers.SampleAuthProvider' threw an exception.
Inner Exception Message: Value cannot be null. Parameter name: format
Can anyone suggest me how to use MOQ to mock above code or any other method to complete test case for this ?
Do not mock what you do not own. GraphServiceClient should be treated as a 3rd party dependency and should be encapsulated behind abstractions you control
You attempted to do that but are still leaking implementation concerns.
The service can be simplified to
public interface IUserServices {
Task<IGroupMembersCollectionWithReferencesPage> GetGroupMembers(string groupID);
}
and the implementation
public class UserServices : IUserServices {
GraphServiceClient GetAuthenticatedClient() {
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await SampleAuthProvider.Instance.GetAccessTokenAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
}));
return graphClient;
}
public Task<IGroupMembersCollectionWithReferencesPage> GetGroupMembers(string groupID) {
var graphClient = GetAuthenticatedClient();
return graphClient.Groups[groupID].Members.Request().GetAsync();
}
}
Which would result in the controller being simplified as well
public class UserController : ApiController {
private readonly IUserServices service;
public UserController(IUserServices myServices) {
this.service = myServices;
}
public async Task<IHttpActionResult> GetGroupMembers([FromUri]string groupID) {
IGroupMembersCollectionWithReferencesPage groupMembers = await service.GetGroupMembers(groupID);
return Ok(groupMembers);
}
}
Now for testing of the controller you can easily mock the abstractions to behave as expected in order to exercise the test to completion because the controller is completely decoupled from the GraphServiceClient 3rd party dependency and the controller can be tested in isolation.
[TestClass]
public class UserControllerShould {
[TestMethod]
public async Task GetGroupMembersCollectionWithReferencePage_Success() {
//Arrange
var groupId = "12345";
var expectedResult = Mock.Of<IGroupMembersCollectionWithReferencesPage>();
var mockService = new Mock<IUserServices>();
mockService
.Setup(_ => _.GetGroupMembers(groupId))
.ReturnsAsync(expectedResult);
var controller = new UserController(mockService.Object);
//Act
var result = await controller.GetGroupMembers(groupId) as System.Web.Http.Results.OkNegotiatedContentResult<IGroupMembersCollectionWithReferencesPage>;
//Assert
Assert.IsNotNull(result);
var actualResult = result.Content;
Assert.AreEqual(expectedResult, actualResult);
}
}
An alternative solution to #Nkosi. Using the constructor public GraphServiceClient(IAuthenticationProvider authenticationProvider, IHttpProvider httpProvider = null); we can Mock the requests actually made.
Complete example below.
Our GraphApiService uses IMemoryCache, to cache both AccessToken and Users from ADB2C, IHttpClientFactory for HTTP requests and Settings is from appsettings.json.
https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-5.0
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0
public class GraphApiService
{
private readonly IHttpClientFactory _clientFactory;
private readonly IMemoryCache _memoryCache;
private readonly Settings _settings;
private readonly string _accessToken;
public GraphApiService(IHttpClientFactory clientFactory, IMemoryCache memoryCache, Settings settings)
{
_clientFactory = clientFactory;
_memoryCache = memoryCache;
_settings = settings;
string graphApiAccessTokenCacheEntry;
// Look for cache key.
if (!_memoryCache.TryGetValue(CacheKeys.GraphApiAccessToken, out graphApiAccessTokenCacheEntry))
{
// Key not in cache, so get data.
var adb2cTokenResponse = GetAccessTokenAsync().GetAwaiter().GetResult();
graphApiAccessTokenCacheEntry = adb2cTokenResponse.access_token;
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(adb2cTokenResponse.expires_in));
// Save data in cache.
_memoryCache.Set(CacheKeys.GraphApiAccessToken, graphApiAccessTokenCacheEntry, cacheEntryOptions);
}
_accessToken = graphApiAccessTokenCacheEntry;
}
public async Task<List<Adb2cUser>> GetAllUsersAsync(bool refreshCache = false)
{
if (refreshCache)
{
_memoryCache.Remove(CacheKeys.Adb2cUsers);
}
return await _memoryCache.GetOrCreateAsync(CacheKeys.Adb2cUsers, async (entry) =>
{
entry.SetAbsoluteExpiration(TimeSpan.FromHours(1));
var authProvider = new AuthenticationProvider(_accessToken);
GraphServiceClient graphClient = new GraphServiceClient(authProvider, new HttpClientHttpProvider(_clientFactory.CreateClient()));
var users = await graphClient.Users
.Request()
.GetAsync();
return users.Select(user => new Adb2cUser()
{
Id = Guid.Parse(user.Id),
GivenName = user.GivenName,
FamilyName = user.Surname,
}).ToList();
});
}
private async Task<Adb2cTokenResponse> GetAccessTokenAsync()
{
var client = _clientFactory.CreateClient();
var kvpList = new List<KeyValuePair<string, string>>();
kvpList.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
kvpList.Add(new KeyValuePair<string, string>("client_id", _settings.AzureAdB2C.ClientId));
kvpList.Add(new KeyValuePair<string, string>("scope", "https://graph.microsoft.com/.default"));
kvpList.Add(new KeyValuePair<string, string>("client_secret", _settings.AzureAdB2C.ClientSecret));
#pragma warning disable SecurityIntelliSenseCS // MS Security rules violation
var req = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{_settings.AzureAdB2C.Domain}/oauth2/v2.0/token")
{ Content = new FormUrlEncodedContent(kvpList) };
#pragma warning restore SecurityIntelliSenseCS // MS Security rules violation
using var httpResponse = await client.SendAsync(req);
var response = await httpResponse.Content.ReadAsStringAsync();
httpResponse.EnsureSuccessStatusCode();
var adb2cTokenResponse = JsonSerializer.Deserialize<Adb2cTokenResponse>(response);
return adb2cTokenResponse;
}
}
public class AuthenticationProvider : IAuthenticationProvider
{
private readonly string _accessToken;
public AuthenticationProvider(string accessToken)
{
_accessToken = accessToken;
}
public Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization", $"Bearer {_accessToken}");
return Task.CompletedTask;
}
}
public class HttpClientHttpProvider : IHttpProvider
{
private readonly HttpClient http;
public HttpClientHttpProvider(HttpClient http)
{
this.http = http;
}
public ISerializer Serializer { get; } = new Serializer();
public TimeSpan OverallTimeout { get; set; } = TimeSpan.FromSeconds(300);
public void Dispose()
{
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return http.SendAsync(request);
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
HttpCompletionOption completionOption,
CancellationToken cancellationToken)
{
return http.SendAsync(request, completionOption, cancellationToken);
}
}
We then use the GraphApiService in various Controllers. Example from a simple CommentController below. CommentService not included but it is not needed for the example anyway.
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class CommentController : ControllerBase
{
private readonly CommentService _commentService;
private readonly GraphApiService _graphApiService;
public CommentController(CommentService commentService, GraphApiService graphApiService)
{
_commentService = commentService;
_graphApiService = graphApiService;
}
[HttpGet("{rootEntity}/{id}")]
public ActionResult<IEnumerable<CommentDto>> Get(RootEntity rootEntity, int id)
{
var comments = _commentService.Get(rootEntity, id);
var users = _graphApiService.GetAllUsersAsync().GetAwaiter().GetResult();
var commentDtos = new List<CommentDto>();
foreach (var comment in comments)
{
commentDtos.Add(CommonToDtoMapper.MapCommentToCommentDto(comment, users));
}
return Ok(commentDtos);
}
[HttpPost("{rootEntity}/{id}")]
public ActionResult Post(RootEntity rootEntity, int id, [FromBody] string message)
{
_commentService.Add(rootEntity, id, message);
_commentService.SaveChanges();
return Ok();
}
}
Since we use our own IAuthenticationProvider and IHttpProvider we can mock IHttpClientFactory based on what URI is called. Complete test example below, check mockMessageHandler.Protected() to see how the requests are mocked. To find the exact request made we look at the documentation. For example var users = await graphClient.Users.Request().GetAsync(); is equivalent to GET https://graph.microsoft.com/v1.0/users.
https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=http#request
public class CommentControllerTest : SeededDatabase
{
[Fact]
public void Get()
{
using (var context = new ApplicationDbContext(_dbContextOptions))
{
var controller = GeCommentController(context);
var result = controller.Get(RootEntity.Question, 1).Result;
var okResult = Assert.IsType<OkObjectResult>(result);
var returnValue = Assert.IsType<List<CommentDto>>(okResult.Value);
Assert.Equal(2, returnValue.Count());
}
}
[Theory]
[MemberData(nameof(PostData))]
public void Post(RootEntity rootEntity, int id, string message)
{
using (var context = new ApplicationDbContext(_dbContextOptions))
{
var controller = GeCommentController(context);
var result = controller.Post(rootEntity, id, message);
var okResult = Assert.IsType<OkResult>(result);
var comment = context.Comments.First(x => x.Text == message);
if(rootEntity == RootEntity.Question)
{
Assert.Equal(comment.QuestionComments.First().QuestionId, id);
}
}
}
public static IEnumerable<object[]> PostData()
{
return new List<object[]>
{
new object[]
{ RootEntity.Question, 1, "Test comment from PostData" }
};
}
private CommentController GeCommentController(ApplicationDbContext dbContext)
{
var userService = new Mock<IUserResolverService>();
userService.Setup(x => x.GetNameIdentifier()).Returns(DbContextSeed.CurrentUser);
var settings = new Settings();
var commentService = new CommentService(new ExtendedApplicationDbContext(dbContext, userService.Object));
var expectedContentGetAccessTokenAsync = #"{
""token_type"": ""Bearer"",
""expires_in"": 3599,
""ext_expires_in"": 3599,
""access_token"": ""123""
}";
var expectedContentGetAllUsersAsync = #"{
""#odata.context"": ""https://graph.microsoft.com/v1.0/$metadata#users"",
""value"": [
{
""businessPhones"": [],
""displayName"": ""Oscar"",
""givenName"": ""Oscar"",
""jobTitle"": null,
""mail"": null,
""mobilePhone"": null,
""officeLocation"": null,
""preferredLanguage"": null,
""surname"": ""Andersson"",
""userPrincipalName"": """ + DbContextSeed.DummyUserExternalId + #"#contoso.onmicrosoft.com"",
""id"":""" + DbContextSeed.DummyUserExternalId + #"""
}
]
}";
var mockFactory = new Mock<IHttpClientFactory>();
var mockMessageHandler = new Mock<HttpMessageHandler>();
mockMessageHandler.Protected()
#pragma warning disable SecurityIntelliSenseCS // MS Security rules violation
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.AbsoluteUri.Contains("https://login.microsoftonline.com/")), ItExpr.IsAny<CancellationToken>())
#pragma warning restore SecurityIntelliSenseCS // MS Security rules violation
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(expectedContentGetAccessTokenAsync)
});
mockMessageHandler.Protected()
#pragma warning disable SecurityIntelliSenseCS // MS Security rules violation
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.AbsoluteUri.Contains("https://graph.microsoft.com/")), ItExpr.IsAny<CancellationToken>())
#pragma warning restore SecurityIntelliSenseCS // MS Security rules violation
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(expectedContentGetAllUsersAsync)
});
var httpClient = new HttpClient(mockMessageHandler.Object);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
var services = new ServiceCollection();
services.AddMemoryCache();
var serviceProvider = services.BuildServiceProvider();
var memoryCache = serviceProvider.GetService<IMemoryCache>();
var graphService = new GraphApiService(mockFactory.Object, memoryCache, settings);
var controller = new CommentController(commentService, graphService);
return controller;
}
}

Categories

Resources