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();
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.
Related
I have a test that looks like this.
namespace Domain.Tests.Unit.Features.ForUser.Auth
{
[TestClass]
public class LoginHandlerTests
{
[TestMethod]
public async Task Should_Succeede_With_Valid_User()
{
// Arrange
var loginCommand = new LoginHandler.LoginCommand
{
Email = "testemail",
Password = "testpassword"
};
var user = new User
{
Email = loginCommand.Email,
UserName = "testname",
};
var userServiceMock = new UserServiceFixture()
.WithSucceededFindByEmailAsync(loginCommand.Email)
.WithSucceededGetRolesAsync(user)
.GetMock();
// Problematic MOCK
var result = new Mock<ISignInServiceResult>()
.SetupProperty(l => l.Succeeded, true);
var signInServiceMock = new Mock<ISignInService>();
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(user, loginCommand.Password))
.Returns(Task.FromResult(result.Object));
var jwtGeneratorServiceMock = new JwtGeneratorServiceFixture()
.WithSucceededGeneration(loginCommand.Email, new string[] { "User" })
.GetMock();
// Here the result is what i expect
//var result1 = await signInServiceMock.Object.CheckPasswordSignInAsync(user, loginCommand.Password);
// Act
var sut = new LoginHandler.Handler(
userServiceMock.Object,
signInServiceMock.Object,
jwtGeneratorServiceMock.Object
);
var loginResponse = await sut.Handle(loginCommand, new CancellationToken());
// Assert
loginResponse.Should().NotBeNull();
loginResponse.Success.Should().BeTrue();
loginResponse.Token.Should().NotBeEmpty();
loginResponse.RefreshToken.Should().NotBeEmpty();
The only problem is that signInServiceMock returns null when the method is called on my handler. If I call it directly on my test i recieve the expected result. But when its called on my handler it always returns null.
I have checked the setup method and the params needed, all seems correct. Any idea? Thanks
The handler is this:
public class Handler : IRequestHandler<LoginCommand, LoginResponse>
{
private readonly IUserService _userService;
private readonly ISignInService _signInService;
private readonly IJwtGeneratorService _jwtGeneratorService;
public Handler(IUserService userService, ISignInService signInService, IJwtGeneratorService jwtGeneratorService)
{
_userService = userService;
_signInService = signInService;
_jwtGeneratorService = jwtGeneratorService;
}
public async Task<LoginResponse> Handle(LoginCommand command, CancellationToken _cancellationToken)
{
var user = await _userService.FindByEmailAsync(command.Email);
if (user is null) throw new InvalidLoginCredentialsException();
ISignInServiceResult checkedPassword
= await _signInService.CheckPasswordSignInAsync(user, command.Password);
if (!checkedPassword.Succeeded) throw new InvalidLoginCredentialsException();
var roles = await _userService.GetRolesAsync(user);
if (roles is null)
throw new UnableToGetRolesException();
ITokenData? token = _jwtGeneratorService.Generate(user.Email, roles);
if (token is null)
throw new LoginTokenGenerationException();
return new LoginResponse
{
Success = true,
Token = token.Token,
RefreshToken = token.RefreshToken
};
}
}
The problem is that your mock setup is not correctly aligned with how your Handler internally uses it.
In your setup you have this:
var user = new User
{
Email = loginCommand.Email,
UserName = "testname",
};
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(user, loginCommand.Password))
.Returns(Task.FromResult(result.Object));
That instructs the mock to return the desired result only upon receiving this exact user instance, while internally in your Handler you're creating another User instance and passing it to the mocked method as follows:
// new User is being created:
var user = await _userService.FindByEmailAsync(command.Email);
// No longer matches your setup:
await _signInService.CheckPasswordSignInAsync(user, command.Password);
Perhaps you meant this, instead:
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(It.IsAny<User>(), loginCommand.Password))
Update:
Actually, the real problem probably lies in a missing setup of your UserService mock.
Although you're calling .WithSucceededGetRolesAsync(user), I believe it doesn't affect the eventual call to FindByEmailAsync(user).
All in all, you have to make sure the call to FindByEmailAsync(user) would indeed return the same user instance in your setup.
I believe what you want is:
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(user, loginCommand.Password))
.ReturnsAsync((User user, string password) => { return result.Object; });
rather than Returns with a Task<T>. Other than that, the code calling the CheckPasswordSignInAsync method may be passing a different argument value than your Mock is configured with, such as a Hash for the password. You can set a breakpoint and inspect that the values passed in actually match what your test configured.
Normally with Mocks you would configure to expect It.IsAny and assert the values using means like:
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(It.IsAny<User>(), It.IsAny<string>()))
.ReturnsAsync((User user, string password) => { return result.Object; });
from here the Returns will received the passed in values if it needs to perform any simple computation to mock out, or you can verify, such as that the user and password passed matched via:
signInServiceMock.Verify(l => l.CheckPasswordSignInAsync(It.Is<User>(u => u.EMail == "testemail"), It.Is<string>(p => p == "testpassword"));
This would at least fail with some description that the method was called, but the expected parameters didn't match.
We have an azure functions project and it looks like the previous dev used a similar approach as described here for unit testing:
http://dontcodetired.com/blog/post/Mocking-HttpRequest-Body-Content-When-Testing-Azure-Function-HTTP-Trigger-Functions
It works well, except that I now need to inject a fake value for the the following string:
HttpContext.Items["MS_AzureFunctionsRequestID"]
cus at some point in the code, we do this:
req.HttpContext.Items["MS_AzureFunctionsRequestID"].ToString()
And right now, the unit test is failing because the fake request object doesn't include this field.
I've tried a few different things but so far, no dice.
Assuming I'm using the following code, where can I inject this fake string:
private static Mock<HttpRequest> CreateMockRequest(object body)
{
var ms = new MemoryStream();
var sw = new StreamWriter(ms);
var json = JsonConvert.SerializeObject(body);
sw.Write(json);
sw.Flush();
ms.Position = 0;
var mockRequest = new Mock<HttpRequest>();
mockRequest.Setup(x => x.Body).Returns(ms);
return mockRequest;
}
I've tried to add an additional "sw.Write()" line and play around with the contents but I can't seem to get it working.
Thanks.
EDIT 1
So I changed the above method so it has one additional line:
private static Mock<HttpRequest> CreateMockRequest(object body)
{
var ms = new MemoryStream();
var sw = new StreamWriter(ms);
var json = JsonConvert.SerializeObject(body);
sw.Write(json);
sw.Flush();
ms.Position = 0;
var mockRequest = new Mock<HttpRequest>();
mockRequest.Setup(x => x.Body).Returns(ms);
mockRequest.Setup(x =>x.HttpContext.Items.Add("MS_AzureFunctionsRequestID", REQID));
return mockRequest;
}
REQID is a new private const that has been defined in the same class as the CreateMockRequest() method.
This is the method that ultimately calls this code:
[Fact]
public async void post_request_should_be_stored_in_queue_and_storage_table(){
var messageQueue = TestFactory.CreateAzureStorageQueue();
var request = TestFactory.CreateWidgetHTTPRequest();
Console.Write(request.HttpContext.Items["MS_AzureFunctionsRequestID"].ToString());
var storageTable = TestFactory.SaveToStorageTable();
var widgetRequest = new WidgetRequest(storageTable);
var response = (OkObjectResult)await widgetRequest.CreateWidget(request, messageQueue, logger);
Assert.NotNull(response);
Assert.True(((AzureStorageQueueTestClient<string>)messageQueue).Messages.Count > 0);
}
When it hits the console.write - the field is null.
Can I call setup on the mock object twice?
how do I set x.body and x.HttpContext?
EDIT 2
I found this post: How to mock HttpContext.Current.Items with NUnit and Rhino Mocks
I'm trying to use the answer to adapt my code, but when I try to create the httpContext object, i'm getting a CS0144 error
"Cannot create an instance of the abstract type or interface 'HttpContext'
"Cannot create an instance of the abstract type or interface 'HttpResponse'
The code I'm playing around with so far just has this:
[Fact]
public void can_create_http_context()
{
const string REQUEST_GUID_FIELD_NAME = "RequestGUID";
var httpContext = new HttpContext(new HttpRequest("", "http://google.com", ""), new HttpResponse(new StringWriter()));
HttpContext.Current = httpContext;
}
I need to write the unit test case around PostAsJsonAsync, which is extension method in HttpClientExtensions. I don't find any easy way to moq it.
Can someone help me in this.
Two ways I can think of:
Use a framework like Moles: https://www.microsoft.com/en-us/research/project/moles-isolation-framework-for-net/ This way you can replace extensions or any other methods with your own, returning the value you want
This is my preferred way. In such cases, wrap the service inside a proxy Interface. You might find it called adapter pattern elsewhere but in my opinion you are just abstracting the actions and proxying the data.
So create the IHttpClientProxy and the corresponding concrete implementation that will use any extention you like. Pass the IHttpClientProxy to your class and mock it as you like.
As mentioned in the comments, there is no way the mocking frameworks to mock static methods this way. Frameworks like Moq, only mock virtual or abstract methods (interface methods are inherently abstract) to guide towards better design.
The issue for me was that I was not understanding that PostAsJsonAsync was actually a convenience method that ultimately calls SendAsync on the handler. So many of the answers you find relating to mocking HTTP client are accurate. The gist is that you are indeed mocking the HttpMessageHandler, and using a concrete HttpClient. The missing piece was that you still need to perform setup and verification on 'SendAsync', not PostAsJsonAsync. Here is the code that worked for me to test PostAsJsonAsync, because SendAsync gets called on the handler:
// Arrange
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("[{'id':1,'value':'1'}]"),
})
.Verifiable();
// new up a real HttpClient, passing in the mocked handler
var httpClient = new HttpClient(handlerMock.Object)
{
BaseAddress = new Uri("http://example.com")
};
// replace with an instance of your actual concrete client
var yourClient = new YourClientHere(httpClient);
// Act
// perform the action on yourClient that makes the PostAsJsonAsync call
// Assert
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(accessRights.Count),
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
);
This article helped me get everything set up correctly: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
I am using NSubstitute for mocking this extension method on HttpClient and it seems seems to work fine without the use of Moles or an adapter.
public class ApiClientTests
{
private HttpClient _client;
private string _url;
private ModelDto _testModel;
public void ApiClientTests()
{
_client = Substitute.For<HttpClient>();
_client.BaseAddress = new Uri("http://fakeUrl/api/")
_url = "Models/";
_testModel = new ModelDto
{
Id = 1,
Name = "Model Name",
Description = "Model Description",
Outputs = new Dictionary<string, ModelOutputDto>(),
Parameters = new Dictionary<string, ModelParamDto>(),
Active = true
};
}
[Fact]
public async Task CreateItemAsync_ValidResponseCode_ReturnsNewResourceUri()
{
// Arrange
var returnUri = $"{_client.BaseAddress}{_url}";
var returnThis = new HttpResponseMessage(System.Net.HttpStatusCode.Created);
returnThis.Headers.Location = new Uri(returnUri);
_client.PostAsJsonAsync(_url, _testModel).ReturnsForAnyArgs(Task.FromResult(returnThis));
var apiClient = new ApiClient<ModelDto>(_client);
// Act
var result = await apiClient.CreateItemAsync(_testModel, _url);
// Assert
await _client.ReceivedWithAnyArgs().PostAsJsonAsync(_url, _testModel);
result.AbsoluteUri.Should().BeEquivalentTo(returnUri);
}
I have the following method in my controller
private string GetPathBase()
{
return _configuration["ApiPathBase"] + $"/v{_httpContext.GetRequestedApiVersion().MajorVersion}";
}
I am not able to mock _httpContext.GetRquestedApiVersion method using moq, as it is an extension method. How can I fill httpContext with test version details, so that the original GetRequestedApiVersion method works?
var controllerContextMock = new Mock<ControllerContext>();
var query = new Mock<IQueryCollection>();
var request = new Mock<HttpRequest>();
var httpContext = new Mock<HttpContext>();
var response = new Mock<HttpResponse>();
query.SetupGet(q => q["api-version"]).Returns(new StringValues("42.0"));
request.SetupGet(r => r.Query).Returns(query.Object);
httpContext.SetupGet(c => c.Request).Returns(request.Object);
httpContext.SetupGet(c => c.Response).Returns(response.Object);
httpContext.SetupProperty(c => c.Items, new Dictionary<object, object>());
httpContext.SetupProperty(c => c.RequestServices, Mock.Of<IServiceProvider>());
controllerContextMock.Object.HttpContext = httpContext.Object;
The HttpContext.GetRequestedApiVersion() extension method is just a shortcut to IApiVersioningFeature.RequestedApiVersion. In all likelihood, you don't need to through all the dependency injection (DI), configuration, request pipeline, or parsing to simulate the requested API version. You can explicitly set it up in a test.
For example:
// arrange
var httpContext = new Mock<HttpContext>();
var features = new FeatureCollection();
IApiVersioningFeature feature = new ApiVersioningFeature(httpContext.Object);
feature.RequestedApiVersion = new ApiVersion(2, 0);
features.Set(feature);
httpContext.SetupGet(c => c.Features).Returns(features);
// act
var result = httpContext.Object.GetRequestedApiVersion();
// assert
result.Should().Be(feature.RequestedApiVersion);
You can setup or modify this approach any number of ways, but ultimately you solve a bunch of problems by simply setting up the feature to what it's expected to resolve to. If you really wanted to, you could even mock the feature:
var feature = new Mock<IApiVersioningFeature>();
feature.SetupProperty(f => f.RequestedApiVersion, new ApiVersion(2, 0));
var features = new FeatureCollection();
var httpContext = new Mock<HttpContext>();
features.Set(feature.Object);
httpContext.SetupGet(c => c.Features).Returns(features);
// TODO: remaining setup
I am very, very new to unit testing and am trying to write a test for a pretty simple method:
public class myClass : RequireHttpsAttribute
{
public override void OnAuthorization(AuthoizationContext filterContext)
{
var request = filterContext.HttpContext.Request;
var header = Convert.ToBoolean(request.Headers["Special-Header-Name"]);
if (!(header || request.IsSecureConnection))
{
HandleNonHttpsRequest(filterContext);
}
}
}
This method, which inherits from the RequireHttpsAttribute, checks if a certain header is present from a page, if it's missing or false, and the page is not secure, then it will call HandleNonHttpsRequest, otherwise it does nothing.
We are using Moq and Nunit for testing. I have found some resources to help build a fakeHttpContext with Moq, but honestly I'm not sure how to use it or where to go within my unit tests to ensure that fake HttpContexts are or are not causing the HandleNonHttpsRequest method to call.
I really appreciate any guidance with this issue.
// arrange
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var headers = new NameValueCollection
{
{ "Special-Header-Name", "false" }
};
request.Setup(x => x.Headers).Returns(headers);
request.Setup(x => x.HttpMethod).Returns("GET");
request.Setup(x => x.Url).Returns(new Uri("http://www.example.com"));
request.Setup(x => x.RawUrl).Returns("/home/index");
context.Setup(x => x.Request).Returns(request.Object);
var controller = new Mock<ControllerBase>();
var actionDescriptor = new Mock<ActionDescriptor>();
var controllerContext = new ControllerContext(context.Object, new RouteData(), controller.Object);
var filterContext = new AuthorizationContext(controllerContext, actionDescriptor.Object);
var sut = new myClass();
// act
sut.OnAuthorization(filterContext);
// assert
Assert.IsInstanceOfType(filterContext.Result, typeof(RedirectResult));
var redirectResult = (RedirectResult)filterContext.Result;
Assert.AreEqual("https://www.example.com/home/index", redirectResult.Url);
Yes, I'd use Moq and create a Mock<AuthorizationContext>. You'll need a series of mock objects to setup the fake request, most notably to specify a NameValueCollection of fake headers.
var request = new Mock<HttpRequestBase>();
request.SetupGet(c => c.Headers).Return(new NameValueCollection{ /* initialize values here */});
request.SetupGet(c => c.IsSecureConnection).Return(/*specify true or false depending on your test */);
var httpContext = new Mock<HttpContextBase>();
httpContext.SetupGet(c => c.Request).Return(request.Object);
var filterContext = new Mock<AuthorizationContext>();
filterContext.SetupGet(c => c.HttpContext).Return(httpContext.Object);
var myclass = new myClass();
myClass.OnAuthorization(filterContext.Object);
(sorry if syntax or usage is slightly off; doing this from the top of my head)
You may need to go in and mock any additional members on filterContext that HandleNonHttpsRequest invokes. I have two recommendations for going about this, as it can sometimes be a hassle if the method you are testing is doing lots of complex stuff on filterContext: 1) check visually and, if it's straight forward enough, mock all the invoked pieces 2) create the myClass.OnAuthorizationRequest, but don't implement any code yet other than the call to HandleNonHttpsRequest. Keep running the test and fixing missing/incorrectly mocked members until the test passes. Then implement your actual logic for OnAuthorizationRequest, testing and fixing (rinse repeat) until it passes.
I encountered an issue with the accepted solution using ASP.NET MVC 4. To resolve it I mocked the http context Items attribute otherwise the sut.OnAuthorization was causing an object is undefined exception:
MockHttpContext.Setup(x => x.Items)
.Returns(new System.Collections.Generic.Dictionary<object, object>());