I'm unit testing a simple post:
public HttpResponseMessage<Document> PostDocument(Document document)
{
document = repository.Add(document);
var response = new HttpResponseMessage<Document>(document, HttpStatusCode.Created);
var uri = Url.Route(null, new { id = document.Id });
response.Headers.Location = new Uri(Request.RequestUri, uri);
return response;
}
However, the 'URL' and 'Request' are obviously going to be null.
Is there an alternative to mocking out ControllerContext and HttpContext?
Update:
Changed it to:
public HttpResponseMessage<Document> PostDocument(Document document,Uri location = null)
{
document = repository.Add(document);
var response = new HttpResponseMessage<Document>(document, HttpStatusCode.Created);
if (location == null)
{
var uri = Url.Route(null, new { id = document.Id });
location = new Uri(Request.RequestUri, uri);
}
response.Headers.Location = location;
return response;
}
Update 2:
This is better:
public HttpResponseMessage<Document> PostDocument(Document document)
{
var uri = Url.Route(null, new { id = document.Id });
var location = new Uri(Request.RequestUri, uri);
return PostDocument(document, location);
}
[NonAction]
public HttpResponseMessage<Document> PostDocument(Document document, Uri location)
{
document = repository.Add(document);
var response = new HttpResponseMessage<Document>(document, HttpStatusCode.Created);
response.Headers.Location = location;
return response;
}
The Request property should be settable, so you only have to set the ControllerContext (which should have a no-arg constructor so you shouldn't even have to mock).
Using FakeItEasy I got it to work doing this in the TestInitialize.
this.Controller.ControllerContext = new System.Web.Http.Controllers.HttpControllerContext();
this.Controller.Request = A.Fake<HttpRequestMessage>();
Your method might recieve HttpRequestMessage as parameter.
public HttpResponseMessage<Document> PostDocument(Document document, HttpRequestMessage message)
{
}
You can take RequestUri from it. In your unit tests you can put test double of HttpRequestMessage object.
Related
I have the following private method allows me to Mock and therfore test a HttpClient
private Mock<HttpClient> GetMockHttClient(HttpStatusCode desiredStatusCode, User user)
{
var httpMessageHandler = new Mock<HttpMessageHandler>();
httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
{
var response = new HttpResponseMessage
{
Content = new StringContent(JsonSerializer.Serialize(user)), //<-- this could be anything
ReasonPhrase = null,
RequestMessage = null,
StatusCode = desiredStatusCode,
Version = new Version(1, 0)
};
return response;
});
var httpClientMock = new Mock<HttpClient>(httpMessageHandler.Object)
{
Object =
{
BaseAddress = new Uri("[Redacted]")
}
};
return httpClientMock;
}
For now I am passing in a User object, however I have other tests where I might for example want an int[] serializing, or a List<User> serializing.
It seems ridiculous to copy and paste this method and change the object I wish to serialize, so how can I make this method generic so that when I call it, I can specify the type that will be serialized to json.
private Mock<HttpClient> GetMockHttClient<TModel>(HttpStatusCode desiredStatusCode, TModel model)
{
var httpMessageHandler = new Mock<HttpMessageHandler>();
httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
{
var response = new HttpResponseMessage
{
Content = new StringContent(JsonSerializer.Serialize<TModel>(model)),
ReasonPhrase = null,
RequestMessage = null,
StatusCode = desiredStatusCode,
Version = new Version(1, 0)
};
return response;
});
var httpClientMock = new Mock<HttpClient>(httpMessageHandler.Object)
{
Object =
{
BaseAddress = new Uri("[Redacted]")
}
};
return httpClientMock;
}
I'm writing some tests for my WebAPI web service and cannot figure out how to send JSON to my service method in the test.
ScheduleRequest sr = new ScheduleRequest();
sr.Months = null;
sr.States = null;
sr.Zip = null;
sr.Miles = null;
sr.PCodes = null;
sr.PageStart = 1;
sr.PageLimit = 10;
HttpRequestMessage m = new HttpRequestMessage();
string sr_ = JsonConvert.SerializeObject(sr);
// How do I load it into the HttpRequestMessage???
// m.Content. = sr_;
var controller = new ShoppingCartController();
// Call the controlelr method and test if the return data is correct.
EventSyncResponse res = (EventSyncResponse)controller.CourseSchedule(m);
Am I doing this correctly, too?
Controller Code:
public object CourseSchedule(ScheduleRequest request)
{
try
{
var result = cart.GetCourseSchedule(request);
return Ok(result);
}
catch (Exception ex)
{
if (ex.Message.StartsWith(#"ORA-20001"))
{
return Ok(new ParticipantResponse { FirstName = "No record found" });
}
throw ex;
}
}
[TestClass]
public class ShoppingCartControllerTests {
[TestMethod]
public void TestCourseSchedule() {
//Arrange
var sr = new ScheduleRequest();
sr.Months = null;
sr.States = null;
sr.Zip = null;
sr.Miles = null;
sr.PCodes = null;
sr.PageStart = 1;
sr.PageLimit = 10;
var json = JsonConvert.SerializeObject(sr);
//construct content to send
var content = new System.Net.Http.StringContent(json, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage {
RequestUri = new Uri("http://localhost/api/shoppingcart"),
Content = content
};
var controller = new ShoppingCartController();
//Set a fake request. If your controller creates responses you will need this
controller.Request = request;
//Act
// Call the controller method and test if the return data is correct.
var response = controller.CourseSchedule(request) as OkNegotiatedContentResult<List<EventSyncResponse>> ;
//Assert
//...other asserts
}
}
But I get the impression that your Action should actually be refactored like this in your controller
public class ShoppingCartController : ApiController {
public IHttpActionResult CourseSchedule(ScheduleRequest model) { ... }
}
which would mean that your isolated unit test should be refactored to...
[TestClass]
public class ShoppingCartControllerTests {
[TestMethod]
public void TestCourseSchedule() {
//Arrange
var sr = new ScheduleRequest();
sr.Months = null;
sr.States = null;
sr.Zip = null;
sr.Miles = null;
sr.PCodes = null;
sr.PageStart = 1;
sr.PageLimit = 10;
var controller = new ShoppingCartController();
//Set a fake request. If your controller creates responses you will need this
controller.Request = new HttpRequestMessage {
RequestUri = new Uri("http://localhost/api/shoppingcart"),
};
//Act
// Call the controller method and test if the return data is correct.
var response = controller.CourseSchedule(sr) as OkNegotiatedContentResult<List<EventSyncResponse>> ;;
//Assert
//...
}
}
MB34.
You need to add in your method, a ScheduleRequest parameter too.
Check this link:
http://www.lybecker.com/blog/2013/06/26/accessing-http-request-from-asp-net-web-api/
I have a web api end point that i want to unit test. I have a custom SwaggerUploadFile attribute that allows a file upload button on the swagger page. But for unit testing I cant figure out how to pass in a file.
For unit testing I am using: Xunit, Moq and Fluent Assertions
Below is my controller with the endpoint:
public class MyAppController : ApiController
{
private readonly IMyApp _myApp;
public MyAppController(IMyApp myApp)
{
if (myApp == null) throw new ArgumentNullException(nameof(myApp));
_myApp = myApp;
}
[HttpPost]
[ResponseType(typeof(string))]
[Route("api/myApp/UploadFile")]
[SwaggerUploadFile("myFile", "Upload a .zip format file", Required = true, Type = "file")]
public async Task<IHttpActionResult> UploadFile()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var provider = await Request.Content.ReadAsMultipartAsync();
var bytes = await provider.Contents.First().ReadAsByteArrayAsync();
try
{
var retVal = _myApp.CheckAndSaveByteStreamAsync(bytes).Result;
if(retVal)
{
return
ResponseMessage(
new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonConvert.SerializeObject(
new WebApiResponse
{
Message = "File has been saved"
}), Encoding.UTF8, "application/json")
});
}
return ResponseMessage(
new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(JsonConvert.SerializeObject(
new WebApiResponse
{
Message = "The file could not be saved"
}), Encoding.UTF8, "application/json")
});
}
catch (Exception e)
{
//log error
return BadRequest("Oops...something went wrong");
}
}
}
Unit test I have so far:
[Fact]
[Trait("Category", "MyAppController")]
public void UploadFileTestWorks()
{
//Arrange
_myApp.Setup(x => x.CheckAndSaveByteStreamAsync(It.IsAny<byte[]>())).ReturnsAsync(() => true);
var expected = JsonConvert.SerializeObject(
new WebApiResponse
{
Message = "The file has been saved"
});
var _sut = new MyAppController(_myApp.Object);
//Act
var retVal = _sut.UploadFile();
var content = (ResponseMessageResult)retVal.Result;
var contentResult = content.Response.Content.ReadAsStringAsync().Result;
//Assert
contentResult.Should().Be(expected);
}
The above fails as when it hits this line if(!Request.Content.IsMimeMultipartContent()) we get a NullReferenceException > "{"Object reference not set to an instance of an object."}"
Best Answer Implemented:
Created an interface:
public interface IApiRequestProvider
{
Task<MultipartMemoryStreamProvider> ReadAsMultiPartAsync();
bool IsMimeMultiPartContent();
}
Then an implementation:
public class ApiRequestProvider : ApiController, IApiRequestProvider
{
public Task<MultipartMemoryStreamProvider> ReadAsMultiPartAsync()
{
return Request.Content.ReadAsMultipartAsync();
}
public bool IsMimeMultiPartContent()
{
return Request.Content.IsMimeMultipartContent();
}
}
Now my controller uses constructor injection to get the RequestProvider:
private readonly IMyApp _myApp;
private readonly IApiRequestProvider _apiRequestProvider;
public MyAppController(IMyApp myApp, IApiRequestProvider apiRequestProvider)
{
if (myApp == null) throw new ArgumentNullException(nameof(myApp));
_myApp = myApp;
if (apiRequestProvider== null) throw new ArgumentNullException(nameof(apiRequestProvider));
_apiRequestProvider= apiRequestProvider;
}
New implementation on method:
[HttpPost]
[ResponseType(typeof(string))]
[Route("api/myApp/UploadFile")]
[SwaggerUploadFile("myFile", "Upload a .zip format file", Required = true, Type = "file")]
public async Task<IHttpActionResult> UploadFile()
{
if (!_apiRequestProvider.IsMimeMultiPartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var provider = await _apiRequestProvider.ReadAsMultiPartAsync();
var bytes = await provider.Contents.First().ReadAsByteArrayAsync();
try
{
var retVal = _myApp.CheckAndSaveByteStreamAsync(bytes).Result;
if(retVal)
{
return
ResponseMessage(
new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(JsonConvert.SerializeObject(
new WebApiResponse
{
Message = "File has been saved"
}), Encoding.UTF8, "application/json")
});
}
return ResponseMessage(
new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(JsonConvert.SerializeObject(
new WebApiResponse
{
Message = "The file could not be saved"
}), Encoding.UTF8, "application/json")
});
}
catch (Exception e)
{
//log error
return BadRequest("Oops...something went wrong");
}
}
}
And my unit test that mocks the ApiController Request:
[Fact]
[Trait("Category", "MyAppController")]
public void UploadFileTestWorks()
{
//Arrange
_apiRequestProvider = new Mock<IApiRequestProvider>();
_myApp = new Mock<IMyApp>();
MultipartMemoryStreamProvider fakeStream = new MultipartMemoryStreamProvider();
fakeStream.Contents.Add(CreateFakeMultiPartFormData());
_apiRequestProvider.Setup(x => x.IsMimeMultiPartContent()).Returns(true);
_apiRequestProvider.Setup(x => x.ReadAsMultiPartAsync()).ReturnsAsync(()=>fakeStream);
_myApp.Setup(x => x.CheckAndSaveByteStreamAsync(It.IsAny<byte[]>())).ReturnsAsync(() => true);
var expected = JsonConvert.SerializeObject(
new WebApiResponse
{
Message = "The file has been saved"
});
var _sut = new MyAppController(_myApp.Object, _apiRequestProvider.Object);
//Act
var retVal = _sut.UploadFile();
var content = (ResponseMessageResult)retVal.Result;
var contentResult = content.Response.Content.ReadAsStringAsync().Result;
//Assert
contentResult.Should().Be(expected);
}
Thanks to #Badulake for the idea
You should do a better separation in the logic of the method.
Refactor your method so it does not depends on any class related to your web framework , in this case the Request class. Your upload code does not need to know anything about it.
As a hint:
var provider = await Request.Content.ReadAsMultipartAsync();
could be transformed to:
var provider = IProviderExtracter.Extract();
public interface IProviderExtracter
{
Task<provider> Extract();
}
public class RequestProviderExtracter:IProviderExtracter
{
public Task<provider> Extract()
{
return Request.Content.ReadAsMultipartAsync();
}
}
In your tests you can easely mock IProviderExtracter and focus in which work is doing each part of your code.
The idea is you get the most decoupled code so your worries are focused only in mocking the classes you have developed, no the ones the framework forces you to use.
The below was how I initially solved it but after Badulake's answer i implemented that where i abstracted the api request to an interface/class and Mocked it out with Moq. I edited my question and put the best implementation there, but i left this answer here for people who dont want to go to the trouble of mocking it
I used part of this guide but I made a simpler solution:
New unit test:
[Fact]
[Trait("Category", "MyAppController")]
public void UploadFileTestWorks()
{
//Arrange
var multiPartContent = CreateFakeMultiPartFormData();
_myApp.Setup(x => x.CheckAndSaveByteStreamAsync(It.IsAny<byte[]>())).ReturnsAsync(() => true);
var expected = JsonConvert.SerializeObject(
new WebApiResponse
{
Message = "The file has been saved"
});
_sut = new MyAppController(_myApp.Object);
//Sets a controller request message content to
_sut.Request = new HttpRequestMessage()
{
Method = HttpMethod.Post,
Content = multiPartContent
};
//Act
var retVal = _sut.UploadFile();
var content = (ResponseMessageResult)retVal.Result;
var contentResult = content.Response.Content.ReadAsStringAsync().Result;
//Assert
contentResult.Should().Be(expected);
}
Private support method:
private static MultipartFormDataContent CreateFakeMultiPartFormData()
{
byte[] data = { 1, 2, 3, 4, 5 };
ByteArrayContent byteContent = new ByteArrayContent(data);
StringContent stringContent = new StringContent(
"blah blah",
System.Text.Encoding.UTF8);
MultipartFormDataContent multipartContent = new MultipartFormDataContent { byteContent, stringContent };
return multipartContent;
}
I have a .NET C# Web API application. I have a single url controller endpoint which receives a POST message. When I run the app and I use an external tool to send the POST message it works perfectly fine.
However, when I trigger the controller from my unit test I get a null ref. exception because for some reason HttpContext.Current is null
This is my current controller (which works in a real scenario):
[HttpPost]
public async Task<IHttpActionResult> Post()
{
await Request.Content.ReadAsStringAsync();
if (Request.Content.IsFormData())
{
var stuff = HttpContext.Current.Request["stuff"];
}
return Ok();
}
}
This is my unit test file:
[TestFixture]
public class AnnotationsControllerTest : BaseIntegrationTest
{
private const string Uri = "http://localhost:2622/api/annotations";
[Test]
public async void TestHistoriesPost()
{
var form = new List<KeyValuePair<string, string>>();
form.Add(new KeyValuePair<string, string>("stuff", "123456"));
using (var request = new HttpRequestMessage(HttpMethod.Post, Uri))
using (var config = new HttpConfiguration())
{
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
using (var content = new FormUrlEncodedContent(form))
{
request.Content = content;
var mockDataService = GetDataServices();
var controller = new AnnotationsController(mockDataService.Object, ApiTestConfiguration());
SetupController(route, controller, config, request);
var actionResult = await controller.Post();
var httpResponseMessage = await actionResult.ExecuteAsync(CancellationToken.None);
Assert.AreEqual(HttpStatusCode.OK, httpResponseMessage.StatusCode);
}
}
}
private static void SetupController(
IHttpRoute route,
ApiController controller,
HttpConfiguration configuration,
HttpRequestMessage request)
{
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "Annotations" } });
controller.ControllerContext = new HttpControllerContext(configuration, routeData, request);
configuration.Services.Replace(typeof(IExceptionHandler), new UnhandledExceptionHandler());
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = configuration;
}
private Mock<IDataServices> GetDataServices()
{
return new Mock<IDataServices>();
}
}
I'm trying to learn webapi and have stumbled across a problem. The training course I was doing showed how to do paging by returning a response header with the next and previous link. However it uses HttpContext.Current.Response.Headers.Add() to send back the next link, previous link, and total pages.
I am also trying to implement unit tests for the controllers. Problem seems to be that the HttpContext.Current is null when running through a unit test. I read somewhere that I shouldn't be HttpContext.Current for webapi as it's not testable, but I'm not sure what I should be using instead.
Here is my contoller code:
public partial class CandidateManagerController
{
private readonly ICandidateManager _candidateManagerV2;
public CandidateManagerController(ICandidateManager candidateManager)
{
_candidateManagerV2 = candidateManager;
}
[VersionedRoute("CandidateManager", 2, Name="CandidateManagerV2")]
public IHttpActionResult Get(int page = 1, int pageSize = 1)
{
try
{
var totalCount = 0;
var totalPages = 0;
var result = _candidateManagerV2.GetCandidates(out totalCount, out totalPages, page, pageSize);
var urlHelper = new UrlHelper(Request);
var prevLink = page > 1
? urlHelper.Link("CandidateManagerV2",
new
{
page = page - 1,
pageSize = pageSize,
})
: "";
var nextLink = page < totalPages ? urlHelper.Link("CandidateManagerV2",
new
{
page = page + 1,
pageSize = pageSize
}) : "";
var paginationHeader = new
{
currentPage = page,
pageSize = pageSize,
totalCount = totalCount,
totalPages = totalPages,
previousPageLink = prevLink,
nextPageLink = nextLink
};
HttpContext.Current.Response.Headers.Add("X-Pagination", Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));
return Ok(result);
}
catch (Exception exp)
{
return InternalServerError();
}
}
}
Here is my unit test. Please note I'm using Nunit and Moq:
[TestFixture]
public class CandidateManagerControllerV2Tests
{
[Test]
[Category("CandidateManagerController Unit Tests")]
public void Should_Return_List_Of_Candidate_Objects()
{
var testList = new List<Candidate>();
testList.Add(new Candidate() { CandidateId = 1, Name = "Mr", Surname = "Flibble" });
testList.Add(new Candidate() { CandidateId = 2, Name = "Arnold", Surname = "Rimmer" });
var totalCount = 0;
var totalPages = 0;
var mockManager = new Mock<ICandidateManager>();
mockManager.Setup(x => x.GetCandidates(out totalCount, out totalPages, It.IsAny<int>(), It.IsAny<int>())).Returns(testList);
var controller = new CandidateManagerController(mockManager.Object);
SetupControllerForTests(controller);
var result = controller.Get(1, 1);
}
private static void SetupControllerForTests(ApiController controller)
{
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/candidatemanager");
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
controller.ActionContext=new HttpActionContext();
}
}
I'm hoping someone will be able to help me. It could be that I've been led down a wrong path with the way to implement paging. However it is likely that I'd need to add a response header for something any way.
You should avoid coupling yourself to HttpContext.
Here is another approach to how you can set the header and still be able to unit test it as you intended. You create a HttpResponseMessage, add headers as needed and then create a ResponseMessageResult from it:
//...code removed for brevity
var response = Request.CreateResponse(HttpStatusCode.OK, result);
response.Headers.Add("X-Pagination", Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));
IHttpActionResult ok = ResponseMessage(response);
return ok;
You should also note that your controller setup will cause a null reference error when creating your UrlHelper because you are resetting the controller's Request to null when you assign the default ActionContext
private static void SetupControllerForTests(ApiController controller) {
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/candidatemanager");
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
//commented this out as it was causing Request to be null
//controller.ActionContext=new HttpActionContext();
}
The following test passed when checking for the X-Pagination header
public async Task Should_Return_Paged_List_Of_Candidate_Objects() {
//Arrange
var testList = new List<Candidate>();
testList.Add(new Candidate() { CandidateId = 1, Name = "Mr", Surname = "Flibble" });
testList.Add(new Candidate() { CandidateId = 2, Name = "Arnold", Surname = "Rimmer" });
var totalCount = 0;
var totalPages = 0;
var mockManager = new Mock<ICandidateManager>();
mockManager.Setup(x => x.GetCandidates(out totalCount, out totalPages, It.IsAny<int>(), It.IsAny<int>())).Returns(testList);
var controller = new CandidateManagerController(mockManager.Object);
SetupControllerForTests(controller);
//Act
var response = await controller.Get(1, 1).ExecuteAsync(System.Threading.CancellationToken.None);
//Assert
Assert.IsNotNull(response);
Assert.IsInstanceOfType(response, typeof(HttpResponseMessage));
Assert.IsTrue(response.Headers.Contains("X-Pagination"));
}
To test your response headers you need to do the following:
Initialize your controller with a ControllerContext in TestInitialize
Call the controller where you add the custom header
Assert it in the TestMethod
It only works if you add your header in the controller class: HttpContext.Response.Headers.Add("x-custom-header", "value");
Example:
public class MyControllerTests
{
private MyController _controller;
[TestInitialize]
public void Setup()
{
_controller= new MyController();
_controller.ControllerContext = new ControllerContext()
{
HttpContext = new DefaultHttpContext(),
};
}
[TestMethod]
public async Task GetAsyncShouldContainCutomHeader()
{
// Act
await _controller.GetAsync().ConfigureAwait(false);
// Assert
Assert.IsTrue(_controller.Response.Headers.ContainsKey("x-custom-header"));
Assert.IsTrue(_controller.Response.Headers["x-custom-header"].Equals("value"));
}
}