I'm trying to run this test:
[TestFixture]
public class AccountManagementViewModelTests
{
[Test, BaseAutoData]
public void OnSync_Should_AddToDatabase_MissingLists(
[Frozen] Mock<IHttpClientService> httpClientService,
[Frozen] Mock<IDataStore<AppModel.List>> dataStore,
List<ApiModel.List> lists,
AccountManagementViewModel sut)
{
// Arrange
httpClientService
.Setup(x => x.GetAsync(It.IsAny<string>()))
.ReturnsAsync(new HttpResponseMessage()
{
Content = JsonContent.Create(lists)
});
// Act
sut.SyncCommand.Execute(null);
// Assert
dataStore.Verify(
d => d.AddItemAsync(
It.Is<AppModel.List>(
x => lists.Any(y => y.Guid == x.ListId))),
Times.Exactly(3));
}
}
Problem is, when I try to deserialize the response, I get 3 empty entities
var allBackedupListsForCurrentUserRequestTask = _httpClientService.GetAsync(string.Format(ListApiEndPoints.GetListsByOwnerEmail, ApplicationUser.Current.Email));
var allBackedupListsForCurrentUserRequest = await allBackedupListsForCurrentUserRequestTask;
var allBackedupListsForCurrentUser = await JsonSerializer.DeserializeAsync<List<ApiModel.List>>(await allBackedupListsForCurrentUserRequest.Content.ReadAsStreamAsync());
If I examine the content of allBackedupListsForCurrentUserRequest.Content I can see all the data there, but none are deserialized into the allBackedupListsForCurrentUser.
The code works fine with real HttpRequests, the issue rises only during unit tests.
What am I missing here?
Other classes involved:
HttpClientService
using ListApp.Services.Interfaces;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace ListApp.Services
{
public class HttpClientService : IHttpClientService
{
private static HttpClient _httpClient = new HttpClient();
public async Task<HttpResponseMessage> GetAsync(string requestUri)
{
return await _httpClient.GetAsync(requestUri);
}
public async Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content)
{
return await _httpClient.PutAsync(requestUri, content);
}
public async Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
{
return await _httpClient.PostAsync(requestUri, content);
}
public void SetBaseAddress(Uri baseAddress)
{
_httpClient.BaseAddress = baseAddress;
}
}
}
BaseAutoDataAttribute
using AutoFixture;
using AutoFixture.AutoMoq;
using AutoFixture.NUnit3;
using System;
namespace ListApp.UnitTests.DataTtributes
{
[AttributeUsage(AttributeTargets.Method)]
internal class BaseAutoDataAttribute : AutoDataAttribute
{
public BaseAutoDataAttribute() : base(() => CreateFixture()) { }
private static IFixture CreateFixture()
{
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization { ConfigureMembers = true, GenerateDelegates = true });
return fixture;
}
}
}
Turns out I'm changing the value of JsonSerializerOptions.PropertyNameCaseInsensitive to true when the app starts but not during the tests.
Setting it to true on the [OneTimeSetUp] did the trick.
((JsonSerializerOptions)typeof(JsonSerializerOptions)
.GetField("s_defaultOptions",
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.NonPublic).GetValue(null))
.PropertyNameCaseInsensitive = true;
Solution by andre-ss6.
Related
I have a Azure Function with 2 triggers:
I’m registering IService in my Startup like so:
I need a different configuration in the Service class depending on which trigger that is calling DoWork()? How can I achieve this using DI?
public class Service : IService
{
public Service(/*Configuration to be injected depends on calling trigger */)
{ }
public void DoWork()
{ }
}
Configuration extract:
Thankyou user1672994. Posting your suggestion as an answer so that it will be helpful for other community members who face similar kind of issues.
Below is the example code to implement todo work items where this will be helpful in resolving your issue.
using AZV3CleanArchitecture.Models;
using AZV3CleanArchitecture.Options;
using AZV3CleanArchitecture.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace AZV3CleanArchitecture.Services
{
public class ToDoItemsService : IToDoItemsService
{
private readonly HttpClient httpClient;
private readonly ToDoItemsServiceOptions toDoItemsServiceOptions;
private readonly ILogger<ToDoItemsService> logger;
public ToDoItemsService(HttpClient httpClient, IOptions<ToDoItemsServiceOptions> toDoItemsServiceOptions, ILogger<ToDoItemsService> logger)
{
this.httpClient = httpClient;
this.toDoItemsServiceOptions = toDoItemsServiceOptions.Value;
this.logger = logger;
}
public async Task<ToDoItem> GetToDoItem(int id)
{
logger.LogInformation($"Retrieving item: {{{Constants.TodoItemId}}}", id);
var getUrl = $"{this.toDoItemsServiceOptions.BaseUrl.TrimEnd('/')}/todos/{id}";
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, getUrl))
{
using (var response = await this.httpClient.SendAsync(requestMessage))
{
string responseString = await response.Content.ReadAsStringAsync();
logger.LogWarning($"Retrieved item: {{{Constants.TodoItemId}}}. Logged as warning for demo.", id);
return JsonConvert.DeserializeObject<ToDoItem>(responseString);
}
}
}
public async Task<IEnumerable<ToDoItem>> GetAllToDoItems(int id)
{
logger.LogInformation($"Retrieving all todo items");
var getUrl = $"{this.toDoItemsServiceOptions.BaseUrl.TrimEnd('/')}/todos";
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, getUrl))
{
using (var response = await this.httpClient.SendAsync(requestMessage))
{
string responseString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<ToDoItem>>(responseString);
}
}
}
public async Task<ToDoItem> CreateToDoItem(ToDoItem toDoItem)
{
// call service and return the output
return await Task.FromResult(new ToDoItem() { Id = 1, UserId = 1, Title = "Some Dummy Title", Completed = true });
}
public Task<ToDoItem> UpdateToDoItem(ToDoItem toDoItem)
{
throw new System.NotImplementedException();
}
}
}
for further information check the ToDoItemServices link.
i am new to integration tests. I have an xUnit project in my solution which contains one test only.
Here's the definition of my test:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "testoweImie",
LastName = "testoweNazwisko",
MailAddress = "test#test.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
And the BaseIntegrationTest class definition:
public abstract class BaseIntegrationTest
{
private const string TestDatabaseName = "TestDatabase";
protected BaseIntegrationTest()
{
var appFactory = new WebApplicationFactory<Startup>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
RemoveDatabaseContextFromServicesCollectionIfFound<EventStoreContext>(services);
RemoveDatabaseContextFromServicesCollectionIfFound<GrantContext>(services);
services
.AddDbContext<EventStoreContext>(options =>
options.UseInMemoryDatabase(TestDatabaseName))
.AddDbContext<GrantContext>(options =>
options.UseInMemoryDatabase(TestDatabaseName));
});
});
HttpClient = appFactory.CreateClient();
}
protected HttpClient HttpClient { get; }
protected static StringContent SerializeObject(object #object) =>
new StringContent(
JsonConvert.SerializeObject(#object),
Encoding.UTF8,
"application/json");
private static void RemoveDatabaseContextFromServicesCollectionIfFound<T>(IServiceCollection services)
where T : DbContext
{
var descriptor = services.SingleOrDefault(service =>
service.ServiceType == typeof(DbContextOptions<T>));
if (!(descriptor is null))
{
services
.Remove(descriptor);
}
}
}
When i run tests, it takes few seconds, and the test ends successfully. The problem is that Resharper Test Runner still runs, although i've already have collected results. what am i doing wrong here? Do i have to somehow dispose the HttpClient, after performing all tests? If so, how to achieve that? Thanks for any help.
It looks like you're actually booting the application inside the test rather than using the testhost (https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1)
public class BasicTests
: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;
public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Notice the IClassFixture stuff.
Background: I am using ASP.NET Core 3.1, and integration testing a REST service that requires cookie authentication.
Candidate solution below.
Note:
The reason I use a vanilla Host instead of TestServer is because of the cookie requirement. When using TestServer, it provides an HttpClient for you, but the client does not pass cookies back to the server.
I also attempted to use a custom HttpClient with TestServer. That consistently generated a System.Net.Sockets.SocketException (No connection could be made because the target machine actively refused it.)
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi; // Contains my Startup.cs
namespace WebApiTest
{
[TestClass]
public class UserTest
{
static IHost HttpHost;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
HttpHost = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
await HttpHost.StartAsync();
}
[ClassCleanup]
public static async Task ClassCleanup()
{
await HttpHost.StopAsync();
}
public static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
return httpContent;
}
public static HttpClient GetCookieHttpClient()
{
SocketsHttpHandler handler = new SocketsHttpHandler
{
AllowAutoRedirect = false,
CookieContainer = new CookieContainer(),
UseCookies = true
};
return new HttpClient(handler);
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (HttpClient client = GetCookieHttpClient())
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.PostAsync("http://localhost:5000/api/auth/login", GetHttpContent(credentials));
response = await client.GetAsync(String.Format("http://localhost:5000/api/users/{0}", credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
HttpClient is a thin-client; it doesn't do anything unless you explicitly tell it to. In other words, it will never send the cookie for you; you must add a Cookie header to the request with the cookie value for each request. The test server "client" is just an HttpClient instance set up to proxy requests to the test server. You should use the test server, as prescribed, along with its client, and then add the Cookie header the requests you make with that.
Solutions based on Chris Pratt's suggestions
After some further digging, Microsoft provides a solution for this (WebApplicationFactory):
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi;
namespace WebApiTest
{
[TestClass]
public class Class2
{
static WebApplicationFactory<Startup> Factory;
static WebApplicationFactoryClientOptions ClientOptions;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
Factory = new WebApplicationFactory<Startup>();
ClientOptions = new WebApplicationFactoryClientOptions();
ClientOptions.AllowAutoRedirect = false;
ClientOptions.HandleCookies = true;
ClientOptions.BaseAddress = new Uri("http://localhost:5000");
}
public static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
return httpContent;
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (HttpClient client = Factory.CreateClient(ClientOptions))
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.PostAsync("http://localhost:5000/api/auth/login", GetHttpContent(credentials));
response = await client.GetAsync(String.Format("http://localhost:5000/api/users/{0}", credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
In case you want to stick with TestServer, here is a manual Cookie-passing implementation:
using Microsoft.AspNetCore.TestHost;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi;
namespace WebApiTest
{
public class CookieHttpClient : IDisposable
{
private static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return httpContent;
}
private static IEnumerable<string> GetCookieStrings(CookieCollection collection)
{
List<string> output = new List<string>(collection.Count);
foreach (Cookie cookie in collection)
{
output.Add(cookie.Name + "=" + cookie.Value);
}
return output;
}
private HttpClient client;
private CookieContainer container;
public CookieHttpClient(HttpClient client)
{
this.client = client;
this.container = new CookieContainer();
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri uri)
{
return await this.SendAsync(method, uri, null);
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri uri, object data)
{
HttpRequestMessage request = new HttpRequestMessage(method, uri);
// Add data
if (data != null)
{
request.Content = GetHttpContent(data);
}
// Add cookies
CookieCollection collection = this.container.GetCookies(uri);
if (collection.Count > 0)
{
request.Headers.Add("Cookie", GetCookieStrings(collection));
}
HttpResponseMessage response = await this.client.SendAsync(request);
// Remember cookies before returning
if (response.Headers.Contains("Set-Cookie"))
{
foreach (string s in response.Headers.GetValues("Set-Cookie"))
{
this.container.SetCookies(uri, s);
}
}
return response;
}
public void Dispose()
{
this.client.Dispose();
}
}
[TestClass]
public class Class1
{
static TestServer TestServer;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
IWebHostBuilder builder = new WebHostBuilder()
.UseStartup<Startup>();
TestServer = new TestServer(builder);
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (CookieHttpClient client = new CookieHttpClient(TestServer.CreateClient()))
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.SendAsync(HttpMethod.Post, new Uri("http://localhost:5000/api/auth/login"), credentials);
response = await client.SendAsync(HttpMethod.Get, new Uri("http://localhost:5000/api/users/" + credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
I am having trouble setting up a unit test where I need to Moq the HttpClient. In my code I have a decorator for the HttpClient which follows an interface.
public class WHttpClient: IWHttpClient{
HttpClient _client = new HttpClient();
...
public async Task<HttpReponseMessage> PostAsJsonAsync<T>(string url, T content)
{
//Do Something
return await _client.PostAsJsonAsync(url, content);
}
...
}
public interface IWHttpClient{
HttpRequestHeaders DefaultRequestHeaders {get;}
Task<HttpResponseMessage> PostAsXmlAsync<T>(string url, T content);
Task<HttpResponseMessage> PostAsJsonAsync<T>(string url, T content);
Task<HttpResponseMessage> PostAsync<T>(string url, T content);
Task<HttpResponseMessage> GetAsync(string url);
Task<T> GetAsync<T>(string url);
Task<T> ReadAsAsync<T>(HttpResponseMessage response);
T Read<T>(HttpResponseMessage response);
}
[TestClass]
public class UnitTest1
{
private class WorkClass
{
private IWHttpClient _client;
public WorkClass(IWHttpClient client)
{
_client = client;
}
public void DoWork()
{
var url = "DUMMY";
var content = new ObjectToSerialize();
Task.Run(() => _client.PostAsJsonAsync(url, content));
}
}
public class ObjectToSerialize
{
}
[TestMethod]
public void TestMethod1()
{
Mock<IWHttpClient> _webClientMock = new Mock<IWHttpClient>(MockBehavior.Strict);
var url = "DUMMY";
var content = new ObjectToSerialize();
_webClientMock.Setup(x => x.PostAsJsonAsync(url, It.IsAny<ObjectToSerialize>())).Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));
var myClassToTest = new WorkClass(_webClientMock.Object);
myClassToTest.DoWork();
}
}
It successfully builds, but when I run the test, it gives me the exception:
System.MissingMethodException: Method not found: 'System.Threading.Tasks.Task'1 SomeNamespace.IWHttpClient.PostAsJsonAsync(System.String, !!0)'.
I've spent hours trying to figure out why I get this exception when I run the test. I have performed a clean and rebuild of my solution and yet it still appears as well as replacing the inputs in the mocksetup with:
(It.IsAny<string>(), It.IsAny<object>())
Does anyone have an idea what's wrong?
Results from running:
The following minimal example was just to try and reproduce your problem as well as demonstrate how to exercise tests like this.
[TestClass]
public class MyTestClass {
private class WorkClass {
private IWHttpClient _client;
public WorkClass(IWHttpClient client) {
_client = client;
}
public async Task DoWork() {
var url = "DUMMY";
var content = new ObjectToSerialize();
var response = await _client.PostAsJsonAsync(url, content);
}
}
public class ObjectToSerialize {
}
[TestMethod]
public async Task MyTestMethod() {
//Arrange
var expectedResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
var _webClientMock = new Mock<IWHttpClient>(MockBehavior.Strict);
_webClientMock
.Setup(_ => _.PostAsJsonAsync(It.IsAny<string>(), It.IsAny<ObjectToSerialize>()))
.ReturnsAsync(expectedResponse)
.Verifiable();
var myClassToTest = new WorkClass(_webClientMock.Object);
//Act
await myClassToTest.DoWork();
//Assert
_webClientMock.Verify();
}
}
When exercised the test behaved as expected and passed. Even when the setup was changed to
.Setup(_ => _.PostAsJsonAsync(It.IsAny<string>(), It.IsAny<object>()))
Review and compare to your current test to help identify where possible mistakes may have been made.
I would like to mockup the RestClient class for test purposes
public class DataServices : IDataServices
{
private readonly IRestClient _restClient;
public DataServices(IRestClient restClient)
{
_restClient = restClient;
}
public async Task<User> GetUserByUserName(string userName)
{
User user = null;
// create a new request
var restRequest = new RestRequest("User", Method.GET);
// create REST parameters
restRequest.AddParameter("userName", userName, ParameterType.QueryString);
// execute the REST request
var restResponse = await _restClient.Execute<User>(restRequest);
if (restResponse.StatusCode.Equals(HttpStatusCode.OK))
{
user = restResponse.Data;
}
return user;
}
}
My test class :
[TestClass]
public class DataServicesTest
{
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
{
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.Returns(new RestResponse<T>
{
Data = JsonConvert.DeserializeObject<T>(json),
StatusCode = httpStatusCode
});
return mockIRestClient.Object;
}
[TestMethod]
public async void GetUserByUserName()
{
var dataServices = new DataServices(MockRestClient<User>(HttpStatusCode.OK, "my json code"));
var user = await dataServices.GetUserByUserName("User1");
Assert.AreEqual("User1", user.Username);
}
}
But I can't instantiate the RestResponse object, I've the following error:
.Returns(new RestResponse<T>
{
Data = JsonConvert.DeserializeObject<T>(json),
StatusCode = httpStatusCode
});
Cannot access protected internal constructor 'RestResponse' here.
How can I workaround this ? I'm using the FubarCoder.RestSharp nuget package on a Xamarin portable Library.
Mock IRestResponse<T> and return that
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
where T : new() {
var data = JsonConvert.DeserializeObject<T>(json)
var response = new Mock<IRestResponse<T>>();
response.Setup(_ => _.StatusCode).Returns(httpStatusCode);
response.Setup(_ => _.Data).Returns(data);
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient
.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.ReturnsAsync(response.Object);
return mockIRestClient.Object;
}
The test should also be updated to be async as well
[TestMethod]
public async Task GetUserByUserName() {
//Arrange
var client = MockRestClient<User>(HttpStatusCode.OK, "my json code");
var dataServices = new DataServices(client);
//Act
var user = await dataServices.GetUserByUserName("User1");
//Assert
Assert.AreEqual("User1", user.Username);
}
I didn't find any great answers so I ended up writing a helper library. I published it to NuGet - MoqRestSharp.Helpers. This project is aimed to help unit test RestSharp as it extends Mock so this helped me test my RestSharp requests and response error handling.
It uses Moq
NuGet Link
Repository Link - Examples are in the project too
Feedback is always welcome!
Complete solution
using Moq;
using Newtonsoft.Json;
using NUnit.Framework;
using RestSharp;
using System.Net;
namespace RestMockTest
{
public class Tests
{
[Test]
public void Test1()
{
var client = MockRestClient<User>(HttpStatusCode.OK, "{\"Name\":\"User1\"}");
var restRequest = new RestRequest("api/item/", Method.POST);
var restResponse = client.Execute<User>(restRequest);
var user = restResponse.Data;
Assert.AreEqual("User1", user.Name);
}
public static IRestClient MockRestClient<T>(HttpStatusCode httpStatusCode, string json)
where T : new()
{
var data = JsonConvert.DeserializeObject<T>(json);
var response = new Mock<IRestResponse<T>>();
response.Setup(_ => _.StatusCode).Returns(httpStatusCode);
response.Setup(_ => _.Data).Returns(data);
var mockIRestClient = new Mock<IRestClient>();
mockIRestClient
.Setup(x => x.Execute<T>(It.IsAny<IRestRequest>()))
.Returns(response.Object);
return mockIRestClient.Object;
}
}
public class User
{
public string Name { get; set; }
}
}