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.
Related
I am moving to use and ActionResult rather than IActionResult so that Swagger automatically picks up my types, but I am getting an error saying that I cannot cast an ActionResult to an OkObjectResult.
How do I cast to an OKObjectResult for testing a 200 status code?
My IActionResult Controller
[HttpGet]
public async Task<IActionResult<IEnumerable<Listing>>> Get()
{
var listings = await listingService.GetAllListings();
if (listings.Any())
{
return Ok(listings);
}
return NotFound();
}
My ActionResult Controller
[HttpGet]
public async Task<ActionResult<IEnumerable<Listing>>> Get()
{
var listings = await listingService.GetAllListings();
if (listings.Any())
{
return Ok(listings);
}
return NotFound();
}
My Test
[Fact]
public async Task ShouldReturnA200StatusCode()
{
var res = (OkObjectResult)await sut.Get();
res.StatusCode.Should().Be(200);
}
Take a look about my solution about how to validate HTTP status code in unit test using XUnit.
[Fact]
public async Task UpdateProduct_When_Product_IsValid_ShouldReturn200()
{
//Arrange
ProductDto productDto = new DataGenerator()
.GenerateProductDto_Valid(1)
.First();
var result = _productAppServiceMock.Setup(p => p
.UpdateAsync(
It.IsAny<Guid>(),
It.IsAny<ProductDto>()))
.ReturnsAsync(() => productDto);
//Act
var itemHttp = await productController
.UpdateProductAsync(productDto.Id, productDto);
//Assert
_productAppServiceMock.Verify(p => p.UpdateAsync(It.IsAny<Guid>(),
productDto), times: Times.Once);
Assert.Equal(typeof(Microsoft.AspNetCore.Mvc.OkObjectResult), itemHttp.GetType());
}
Following the guidance of this answer to a similar question you need to cast the Result of the .Get() method (rather than just the .Get() method) and then you can check that for the 200 OK StatusCode
[Fact]
public async Task ShouldReturnA200StatusCode()
{
var res = await sut.Get();
var okObj = res.Result as ObjectResult;
okObj.StatusCode.Should().Be(StatusCodes.Status200OK);
}
Following on from the comments above I have used the ObjectResult type with the aim to not coerce the 200 status code and have used the as casting.
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>());
}
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'm trying to unit test an action in an MVC controller but the mocked object is returning null.
I have a ClientProxy interface
public interface IClientProxy
{
Task<T> Get<T>(string uri);
}
With the following concrete implementation
public async Task<T> Get<T>(string uri)
{
using (HttpClient httpClient = new HttpClient())
{
ConfigureClient(httpClient);
var response = await httpClient.GetAsync(uri);
response.EnsureSuccessStatusCode();
return await Task.Run(() => GetResultFromResponse<T>(response));
}
}
This is my action
public async Task<ActionResult> Index()
{
var categories = await _proxy.Get<PagedResults<Category>>("/category/get");
return View("index", null, JsonConvert.SerializeObject(categories));
}
And this is the unit test class
public class CategoryControllerTests
{
private readonly CategoryController _controller;
private readonly Mock<IClientProxy> _mockProxy;
public CategoryControllerTests()
{
_mockProxy = new Mock<IClientProxy>();
_controller = new CategoryController(_mockProxy.Object);
}
[Fact]
public async Task IndexPageRenders()
{
// Arrange
var fakeResult = new PagedResults<Category>
{
Paging = new Paging
{
Page = 1,
PageSize = 10,
TotalRecords = 2
},
Results = new List<Category>
{
new Category { CategoryId = 1, Name = "Category One" },
new Category { CategoryId = 2, Name = "Category Two" }
}
};
var result = JsonConvert.SerializeObject(fakeResult);
_mockProxy.Setup(p => p.Get<PagedResults<Category>>(It.IsAny<string>())).ReturnsAsync(fakeResult);
// Act
var action = await _controller.Index() as ViewResult;
// Assert
_mockProxy.Verify(p => p.Get<PagedResults<Category>>(It.IsAny<string>()), Times.Exactly(1));
}
}
Currently the mocked object setup for the get method is not getting hit, and categories are null (the Verify is failing, implying that the mocked method is never called). However, if I remove the await keyword from the action then the categories are returned. I'd rather not remove the await just to pass the test as it's there for a good reason, so any help would be appreciated.
Thanks in advance.
Currently the mocked object setup for the get method is not getting hit, and categories are null (the Verify is failing, implying that the mocked method is never called).
That would mean that the mocked object was not passed as a dependency to the controller being tested and as such is not being invoked when the test is being exercised.
Here is a Minimal, Complete, and Verifiable example that demonstrates how one would setup and exercise a test in such a scenario.
The follow were used
public class MyModel {
public int MyIntProperty { get; set; }
public string MyStringProperty { get; set; }
}
public interface IClientProxy {
Task<T> Get<T>(string uri);
}
public class MyController : Controller {
IClientProxy _proxy;
public MyController(IClientProxy _proxy) {
this._proxy = _proxy;
}
public async Task<ActionResult> Index() {
var categories = await _proxy.Get<MyModel>("/category/get");
return View(categories);
}
}
And following test was used with MSTest, Moq and Fluent Assertions where is flowed to completion and passed.
[TestClass]
public class MyController_Should {
[TestMethod]
public async Task _Render_Index_Page() {
// Arrange
var fakeResult = new MyModel {
MyIntProperty = 0,
MyStringProperty = "Hello World."
};
var _mockProxy = new Mock<IClientProxy>();
_mockProxy
.Setup(_ => _.Get<MyModel>(It.IsAny<string>()))
.ReturnsAsync(fakeResult);
var _controller = new MyController(_mockProxy.Object);
// Act
var action = await _controller.Index() as ViewResult;
// Assert
_mockProxy.Verify(_ => _.Get<MyModel>(It.IsAny<string>()), Times.Exactly(1));
action.Should().NotBeNull();
var model = action.Model;
model.Should().NotBeNull()
.And.BeOfType<MyModel>()
.And.Be(fakeResult);
}
}
I would suggest reviewing this example and then comparing it to how you designed your test to see if you can identify what may be causing your problem.
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");