I have a controller in a .NET Core v3 web API project
public class MyController : ControllerBase
{
private readonly IService service;
public MyController (IService service)
{
this.service= service;
}
HttpGet("{id}")]
public async Task<ActionResult<MyModel>> Get(int id)
{
var record= await service.GetAsync(id);
if (record== null)
return NotFound();
return Ok(Convert to model before returning);
}
}
I'm trying to write a unit test for the Get method using NUnit.
This is what I have and it works:
[Test]
public void Get_WhenCalled_ReturnNotFound()
{
service = new Mock<IService>();
controller = new MyController(service.Object);
service.Setup(service => service.GetAsync(1)).ReturnsAsync((MyType)null);
var result = controller.Get(1);
Assert.That(result.Result.Result, Is.TypeOf<NotFoundResult>());
}
But in the assert I have to call result.Result.Result. It looks a bit odd. Can I get around this?
I've also tried the following line but it's the same:
service.Setup(service => service.GetAsync(1)).Returns(Task.FromResult((MyType)null));
You can reduce 1 Result by writing the test using async/await.
[Test]
public async Task Get_WhenCalled_ReturnNotFound()
{
service = new Mock<IService>();
controller = new MyController(service.Object);
service.Setup(service => service.GetAsync(1)).ReturnsAsync((MyType)null);
var result = await controller.Get(1);
Assert.That(result.Result, Is.TypeOf<NotFoundResult>());
}
Related
I'm trying to write some test with XUnit, specifically I'd like to have a test that ensures that when a certain exception is thrown it gets remapped into a meaningful error code.
I already set up the Global error handling middleware and it works correctly.
Here there is some example code of how my solution works:
My controller with a post endpoint that can return 200 or 404
//Controller
[HttpPost]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<StatusCodeResult> Create([FromBody] Request request) {
//Process request
handler.Handle(request);
return Ok();
}
The Middleware for the Global error handling that remaps exceptions into Error codes
//StartUp Middleware
app.UseExceptionHandler(builder => {
builder.Run(handler: async context => {
IExceptionHandlerFeature error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null) {
int statusCode = (int)GetStatusCodeForException(error.Error);
context.Response.StatusCode = statusCode;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(new ErrorDetails { StatusCode = statusCode, Message = error.Error.Message }.ToString());
}
});
});
And then my test in where I arrange some mocks, instantiate the controller and call the Create method
//UnitTest
[Fact]
public async Task Test()
{
//Arrange
var mockHandler = new Mock<IHandler>();
mockHandler.Setup(handler => handler.Handle(It.IsAny<Request>())).Throws(new CustomException(It.IsAny<string>()));
MyController myController = new MyController();
//Act
var statusCodeResult = await myController.Create(request);
//Assert
StatusCodeResult result = Assert.IsType<NotFoundResult>(statusCodeResult);
}
Here I want to ensure that the CustomException is remapped into a 404 status code. How do I do it? Any help is appreciated.
In your test the middleware is not available. You need to spin up a hosting environment to do that, the package Microsoft.AspNetCore.TestHost provides you with one that you can use for testing:
[Fact]
public async Task Test1()
{
using var host = new TestServer(Program.CreateHostBuilder(null));
var client = host.CreateClient();
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/controller");
var result = await client.SendAsync(requestMessage);
var status = result.StatusCode;
// TODO: assertions
}
Now when you call your API in a way an exception is thrown, the middleware should be executed and covered.
You can use the WebApplicationFactory class from the Microsoft.AspNetCore.Mvc.Testing nuget package. This bootstraps your application in-memory to allow for end to end functional tests. Rather than calling individual action methods you can make calls via HttpClient to ensure all middleware etc is called.
You use the Startup class that you have already defined in your main entry project and can add mock/fake objects to the IoC container as required. This allows you to verify/setup any dependencies.
You can then inject this as an IClassFixture. Once injected calling .CreateClient() on the instance will return an HttpClient, through which you can make requests.
Here is an example implementation:
// app factory instance
public class TestAppFactory : WebApplicationFactory<Startup>
{
// mock for setup/verify
public Mock<IHandler> MockHandler { get; } = new Mock<IHandler>();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton(MockHandler);
});
}
}
public class MyTestClass : IClassFixture<TestAppFactory>
{
private readonly TestAppFactory _factory;
private readonly HttpClient _client;
private readonly Mock<IHandler> _mockHandler;
public MyTestClass(TestAppFactory factory)
{
_factory = factory;
_client = factory.CreateClient();
_mockHandler = factory.MockHandler;
}
[Fact]
public async Task Test()
{
// make calls via _client
}
}
So I have a controller that is using HttpClient to call a webservice like so:
public class DemoController : Controller
{
HttpClient client;
string baseUrl = "http://localhost:90/webservice";
public DemoController()
{
client = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
}
// GET: DemoInfo
public async Task<ActionResult> Index()
{
HttpResponseMessage response = await client.GetAsync(baseUrl + "vehicle/menu/year");
string content = "";
MenuItems result = null;
if (response.IsSuccessStatusCode)
{
content = await response.Content.ReadAsStringAsync();
result = (MenuItems)new XmlSerializer(typeof(MenuItems)).Deserialize(new StringReader(content));
}
return View("Index", result);
}
}
My unit test for this action is as follows:
[TestMethod]
public async Task Test_Index()
{
// Arrange
DemoController controller = new DemoController();
// Act
var result = await controller.Index();
ViewResult viewResult = (ViewResult) result;
// Assert
Assert.AreEqual("Index", viewResult.ViewName);
Assert.IsNotNull(viewResult.Model);
}
So obviously I would like to avoid making the web service call every time the test is run. Would I be on the right track in opting for an IoC container like Unity so that HttpClient would be injected into the controller? Is that overkill for what I'm trying to achieve? I'm aware that there is a lot of history with people struggling with properly mocking httpclient in there unit tests through this github issue. Any help would be greatly appreciated in giving some insight into how to write the controller to make a service call while still being testable.
All dependencies which makes tests slow should be abstracted.
Wrap HttpClient with an abstraction, which you can mock in your tests.
public interface IMyClient
{
Task<string> GetRawDataFrom(string url);
}
Then your controller will depend on that abstraction
public class DemoController : Controller
{
private readonly IMyClient _client;
private string _baseUrl = "http://localhost:90/webservice";
public DemoController(IMyClient client)
{
_client = client;
}
public async Task<ActionResult> Index()
{
var rawData = _client.GetRawDataFrom($"{_baseUrl}vehicle/menu/year");
using (var reader = new StringReader(rawData))
{
var result =
(MenuItems)new XmlSerializer(typeof(MenuItems)).Deserialize(reader);
return View("Index", result);
}
}
}
Then in tests you can mock your abstraction to return expected data
public class FakeClient : IMyClient
{
public string RawData { get; set; }
public Task<string> GetRawDataFrom(string url)
{
return Task.FromResult(RawData);
}
}
[TestMethod]
public async Task Test_Index()
{
// Arrange
var fakeClient = new FakeClient
{
RawData = #"[
{ Name: "One", Path: "/one" },
{ Name: "Two", Path: "/two" }
]"
};
DemoController controller = new DemoController(fakeClient);
// Act
var result = await controller.Index();
ViewResult viewResult = (ViewResult)result;
// Assert
Assert.AreEqual("Index", viewResult.ViewName);
Assert.IsNotNull(viewResult.Model);
}
Actual implementation will use HttpClient
public class MyHttpClient : IMyClient
{
public Task<string> GetRawDataFrom(string url)
{
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
}
}
An alternative approach to testing HttpClient calls without service wrappers, mocks, or IoC containers is to use Flurl, a small wrapper library around HttpClient that provides (among other things) some robust testing features. [Disclaimer: I'm the author]
Here's what your controller would look like. There's a few ways to do this, but this approach uses string extension methods that abstract away the client entirely. (A single HttpClient instance per host is managed for you to prevent trouble.)
using Flurl.Http;
public class DemoController : Controller
{
string baseUrl = "http://localhost:90/webservice";
// GET: DemoInfo
public async Task<ActionResult> Index()
{
var content = await baseUrl
.AppendPathSegment("vehicle/menu/year")
.GetStringAsync();
var result = (MenuItems)new XmlSerializer(typeof(MenuItems)).Deserialize(new StringReader(content));
return View("Index", result);
}
}
And the test:
using Flurl.Http;
[TestMethod]
public async Task Test_Index()
{
// fake & record all HTTP calls in the test subject
using (var httpTest = new HttpTest())
{
// Arrange
httpTest.RespondWith(200, "<xml>some fake response xml...</xml>");
DemoController controller = new DemoController();
// Act
var result = await controller.Index();
ViewResult viewResult = (ViewResult) result;
// Assert
Assert.AreEqual("Index", viewResult.ViewName);
Assert.IsNotNull(viewResult.Model);
}
}
Flurl.Http is available on NuGet.
I have a ASP.NET Core MVC API with controllers that need to be unit tested.
Controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace TransitApi.Api.Controllers
{
[Route("api/foo")]
public class FooController : Controller
{
private IFooRepository FooRepository { get; }
public FooController(IFooRepository fooRepository)
{
FooRepository = fooRepository;
}
[HttpGet]
[Authorize("scopes:getfoos")]
public async Task<IActionResult> GetAsync()
{
var foos = await FooRepository.GetAsync();
return Json(foos);
}
}
}
It is essential that I am able to unit test the effectiveness of the AuthorizeAttribute. We have had issues in our code base with missing attributes and incorrect scopes. This answer is exactly what I am looking for, but not having a ActionInvoker method in Microsoft.AspNetCore.Mvc.Controller means I am not able to do it this way.
Unit Test:
[Fact]
public void GetAsync_InvalidScope_ReturnsUnauthorizedResult()
{
// Arrange
var fooRepository = new StubFooRepository();
var controller = new FooController(fooRepository)
{
ControllerContext = new ControllerContext
{
HttpContext = new FakeHttpContext()
// User unfortunately not available in HttpContext
//,User = new User() { Scopes = "none" }
}
};
// Act
var result = controller.GetAsync().Result;
// Assert
Assert.IsType<UnauthorizedResult>(result);
}
How can I unit test that users without the correct scopes are denied access to my controller method?
Currently I have settled for testing merely the presence of an AuthorizeAttribute as follows, but this is really not good enough:
[Fact]
public void GetAsync_Analysis_HasAuthorizeAttribute()
{
// Arrange
var fooRepository = new StubFooRepository();
var controller = new FooController(fooRepository)
{
ControllerContext = new ControllerContext
{
HttpContext = new FakeHttpContext()
}
};
// Act
var type = controller.GetType();
var methodInfo = type.GetMethod("GetAsync", new Type[] { });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
// Assert
Assert.True(attributes.Any());
}
This would need integration testing with an in-memory test server because the attribute is evaluated by the framework as it processes the request pipeline.
Integration testing in ASP.NET Core
Integration testing ensures that an application's components function correctly when assembled together. ASP.NET Core supports integration testing using unit test frameworks and a built-in test web host that can be used to handle requests without network overhead.
[Fact]
public async Task GetAsync_InvalidScope_ReturnsUnauthorizedResult() {
// Arrange
var server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
var client = server.CreateClient();
var url = "api/foo";
var expected = HttpStatusCode.Unauthorized;
// Act
var response = await client.GetAsync(url);
// Assert
Assert.AreEqual(expected, response.StatusCode);
}
You can also create a start up specifically for the test that will replace any dependencies for DI with stubs/mocks if you do not want the test hitting actual production implementations.
What you could do, is to configure your testserver to add an anonymous filter middleware:
private HttpClient CreatControllerClient()
{
return _factory.WithWebHostBuilder(builder
=> builder.ConfigureTestServices(services =>
{
// allow anonymous access to bypass authorization
services.AddMvc(opt => opt.Filters.Add(new AllowAnonymousFilter()));
})).CreateClient();
}
First remeove IAuthorizationHandler
var authorizationDescriptor = services.FirstOrDefault(d => d.ServiceType == typeof(IAuthorizationHandler));
if (authorizationDescriptor != null)
services.Remove(authorizationDescriptor);
Then add
services.AddScoped<IAuthorizationHandler, TestAllowAnonymous>();
public class TestAllowAnonymous : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
context.Succeed(requirement); //Simply pass all requirementsreturn Task.CompletedTask;
return Task.CompletedTask;
}
}
I need to mock this C# WebApi class using the Moq framework
public class PvValuesController
{
private readonly IDataServices dataService;
public PvValuesController(IDataServices dataServices)
{
dataService = dataServices;
}
[HttpGet]
public IHttpActionResult Get(string id, string st, string et)
{
if (dataService == null)
{
return BadRequest("DataService not found");
}
var result = dataService.GetPvData(id, st, et);
return Ok(result);
}
}
Problem is, if I mock it like this:
var controllerMock = new Mock<PvValuesController>();
I'm not passing any DataService to it, so the Get() function will always return a bad request code.
The original line was:
var controller = new PvValuesController(dataService);
Where dataService is a concrete instance of IDataService and is a complex object
How can I effectively mock such class?
EDIT:
my new test code:
[Test]
public async void TestMoq()
{
var a = new List<dynamic>();
a.Add(23);
// arrange
var dataService = new Mock<IDataServices>();
dataService.Setup(l => l.GetPvData(It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<DateTime>())).Returns(a);
var controller = new PvValuesController(dataService.Object);
// act
var actionResult = controller.Get("groupid", "timestampstart", "timestampend");
var httpResponseMessage = await actionResult.ExecuteAsync(CancellationToken.None);
// assert
Assert.AreEqual(HttpStatusCode.BadRequest, httpResponseMessage.StatusCode);
}
I get an exception on the await line:
System.InvalidOperationException: HttpControllerContext.Configuration must not be null
Mock your dependency interface as shown below
var service = new Mock<IDataServices>();
Inject it to your controller
var ctrlObj = new PvValuesController(service.Object);
Then continue with your setup as usual for the service
service.SetUp(l => l.Get()).Returns("Hello World!!!");
Finally call your controller method using the controller instance
ctrlObj.Get("A","B","C");
I'm trying to create a custom IHttpActionResult type in web api 2 that will return content as HTML instead of json. What I'm struggling with is how to unit test an ApiController that returns my new ActionResult type. Many example showing how to unit test an ApiController tells you to cast it to OkNegotiatedContentResult and then read the content property off it but this doesn't seem to work in my case. When I debug the test, the code block in ExecuteAsync never seems to be called. Do I need to do this explicitly in my unit tests? Any help would be much appriciated
This is how my ActionResult looks like
public class HtmlActionResult : IHttpActionResult
{
String _html;
public HtmlActionResult(string html)
{
_html = html;
}
public Task<System.Net.Http.HttpResponseMessage> ExecuteAsync(System.Threading.CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent(_html );
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/html");
return Task.FromResult(response);
}
}
This is my ApiController
public class HomeController : ApiController
{
public IHttpActionResult Get(string page)
{
return new HtmlActionResult("<html></html>");
}
}
And this is my test method
[TestMethod]
public async Task Get()
{
//Arrenge
HomeController controller = new HomeController();
//Act
IHttpActionResult result = controller.Get();
//Assert
//Assert.IsNotNull(result.Content);
}
Use Fluent Assertions:
IHttpActionResult result = await controller.Get()
HtmlActionResult htmlActionResult = result.Should()
.BeOfType<HtmlActionResult>()
.Which; // <-- the key feature
// or .And
what means you can nest assertions:
IHttpActionResult result = await controller.Get()
result.Should().BeOfType<HtmlActionResult>()
.Which.Content.Should().Be(expected);
Or just as #Spock suggested test things separately:
Get() returns IHttpActionResult which is actually HtmlActionResult
How HtmlActionResult works independently from Get()
You are not waiting for the controller action to complete - you should change your test to something like (this is untried):
public async Task Get()
{
// Arrange
HomeController controller = new HomeController();
// Act
IHttpActionResult result = await controller.Get();
// Assert
Assert.IsNotNull(result.Content);
}
Assuming that you're using WebAPI version 2, there is a really good guide on how to Unit Test Controllers on The ASP.NET Site.
I was in a similar scenario to you, and was a bit iffy about making my controller methods return Tasks instead of IHttpActionResults - which I believe are much cleaner.
I managed to adapt the code under the Testing Actions that Return HttpResponseMessage section of the above link to get my unit test working as expected.
Below is a simplified outline of my scenario:
public class XyzController : ApiController
{
private readonly IDbContext _dbContext;
public XyzController(IDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
[Route("this-is-optional")]
public IHttpActionResult Get(<optional-params-here>)
{
// Do work
...
// My data is an array of objects
return Ok(data);
}
}
[TestFixture]
public class XyzControllerTest
{
[Test]
public void Get_ReturnsSuccessfully()
{
// Arrange
IDbContext testContext = MockDbContext.Create();
...
// Populate textContext here
...
XyzController xyzController = new XyzController(testContext)
{
// These are required or exceptions will be thrown
Request = new HttpRequestMessage();
Configuration = new HttpConfiguration()
};
...
// Act
var response = xyzController.Get(<params-if-required>).ExecuteAsync(CancellationToken.None);
// Assert
Assert.IsNotNull(response);
Assert.IsTrue(response.IsCompleted);
Assert.AreEqual(HttpStatusCode.OK, response.Result.StatusCode);
// Assertions on returned data
MyModel[] models;
Assert.IsTrue(response.Result.TryGetContentValue<MyModel[]>(out models));
Assert.AreEqual(5, model.Count());
Assert.AreEqual(1, model.First().Id);
...
}
}
Try this:
[TestMethod]
public void Get()
{
//Arrenge
var controller = new HomeController();
//Act
var result = controller.Get().Result as HtmlActionResult;
//Assert
Assert.IsNotNull(result);
}
Notice that your test can be void and you don't have to await your Get method, you can use.Result to run the task.
Also I am casting the result to HtmlActionResult, which will end up result to be Null if the result was a different ActionResult like OkResult, or NotFoundResult,...etc.
Hope that helps.