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;
}
Related
I am trying to write an unit test at the moment and i am having difficult with the mocking the RestSharp. The test i am trying to write is for the GetAll method.
This is the code that i am trying to test.
public class Client: IClient
{
public IRestClient RestClient { get; set; }
public IOptions<ClientSettings>Settings { get; set; }
public Client(IOptions<ClientSettings>options)
{
Settings = options;
RestClient = new RestClient(options.Value.BaseUrl);
}
public async Task<List<EventDTO>> GetAll()
{
var allEvents = await RetrieveAllEvents();
var data = TransformData(allEvents);
return data;
}
private static List<EventDTO> TransformData(IEnumerable<Event> allEvents)
{
var data = allEvents.SelectMany(con =>
con.Geometries.Select(geo =>
new EventDTO
{
Title = con.Title,
Id = con.Sources.FirstOrDefault()?.Id,
CategoriesTitle = con.Categories.FirstOrDefault()?.Title,
Closed = con.Closed,
DateTime = geo.Date
})
).ToList();
return data;
}
private async Task<IEnumerable<Event>> RetrieveAllEvents()
{
var openEvents = await RetrieveEvent(Settings.Value.GetAllOpen);
var closedEvents = await RetrieveEvent(Settings.Value.GetAllClosed);
var allEvents = openEvents.Events.Concat(closedEvents.Events);
return allEvents;
}
private async Task<RootObject> RetrieveEvent(string request)
{
var responseData = new RestRequest(request, Method.GET);
var content = await RestClient.GetAsync<RootObject>(responseData);
return content;
}
}
When the code gets to this line, it just stops working. I tried putting in a try and catch around it see what the error is but it just blows the stack.
var data = await RestClient.GetAsync<RootObject>(responseData);
I saw an example online and i tried mocking the RestSharp
restClient.Setup(c => c.ExecuteAsync<EventDTO>(
Moq.It.IsAny<IRestRequest>(),
Moq.It.IsAny<Action<IRestResponse<EventDTO>, RestRequestAsyncHandle>>()))
.Callback<IRestRequest, Action<IRestResponse<EventDTO>, RestRequestAsyncHandle>>((request, callback) =>
{
var responseMock = new Mock<IRestResponse<EventDTO>>();
responseMock.Setup(r => r.Data).Returns(new EventDTO() { });
callback(responseMock.Object, null);
});
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'm trying to write a unit test for polly, but it looks like the return is cached.
Method PostAsyncWithRetry:
using Polly;
using System;
using System.Diagnostics;
using System.Net.Cache;
using System.Net.Http;
public class RetryClient
{
private HttpClient httpClient = new HttpClient(new WebRequestHandler()
{ CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore) });
public HttpResponseMessage PostAsyncWithRetry(
String url,
String path,
StringContent httpContent)
{
httpClient.BaseAddress = new Uri(url);
var retryPolicy =
Policy.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.RetryAsync(3, (exception, retryCount, context) =>
{
Debug.WriteLine("RetryCount: {0}", retryCount);
});
var response = retryPolicy.ExecuteAsync(async () =>
{
return await httpClient.PostAsync(path, httpContent);
}
);
return response.Result;
}
}
Test:
[TestFixture]
class Test
{
private HttpClient mockHTTPClient;
private Mock<WebRequestHandler> mockHttpMessageHandler;
private RetryClient testInstance;
private const String URL = "https://aaa.com";
private const String PATH = "/path";
private const String EXPECTED_STRING_CONTENT = "Some return text";
[SetUp]
public void SetUp()
{
testInstance = new RetryClient();
mockHttpMessageHandler = new Mock<WebRequestHandler>();
mockHttpMessageHandler.Object.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
mockHTTPClient = new HttpClient(mockHttpMessageHandler.Object);
var type = typeof(RetryClient);
var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
fields[0].SetValue(testInstance, mockHTTPClient);
}
[Test]
public void TestMEEEE()
{
var responses = new Queue<Task<HttpResponseMessage>>();
responses.Enqueue(Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.NotFound,
Content = new StringContent(EXPECTED_STRING_CONTENT)
}));
responses.Enqueue(Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(EXPECTED_STRING_CONTENT)
}));
var postContent = new StringContent(EXPECTED_STRING_CONTENT);
mockHttpMessageHandler.Protected()
.Setup<Task>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns(responses.Dequeue());
var response = testInstance.PostAsyncWithRetry(
URL, PATH, postContent);
mockHttpMessageHandler.Verify();
Assert.AreEqual(responses.Count, 0, "didn't dequeue");
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Incorrect status code");
}
}
Not sure why, but it looks like the responses queue is only being Dequeue once, this leads me to believe the response is being cache. Does anyone know who is caching the response, and how do I disable it?
Thanks a bunch in advance!
I figured it out. It has nothing to do with caching. During the mock setup, it stores the Dequeue value as a return instead of invoking it every time.
Changing it to () => responses.Dequeue() works now.
Thank you guys!
I am using Moq in .net core(1.1) and having a bit of torrid time understanding this behavior as all the examples on interweb points to the fact the this should work with no issues.
I have already tried with:
Returns(Task.FromResult(...)
Returns(Task.FromResult(...)
ReturnsAsync(...)
Mocking a IHttpClient interface to wrap PostAsync, PutAsync and GetAsync. All of these return an ApiResponse object.
var mockClient = new Mock<IHttpClient>();
Does not work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(new ApiResponse()));
PostSync definition:
public async Task<ApiResponse> PostAsync(string url, string body, string authToken = null)
Does work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(bool));
PostSync definition:
public async Task<bool> PostAsync(string url, string body, string authToken = null)
Usage:
var api = new ApiService(mockClient.Object);
var response = api.LoginAsync(body.Username, body.Password);
UPDATE
[Fact]
public async void TestLogin()
{
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null)).Returns(Task.FromResult(new ApiResponse()));
var api = new ApiService(mockClient.Object);
var response = await api.LoginAsync(body.Username, body.Password);
Assert.IsTrue(response);
}
Return Type:
public class ApiResponse
{
public string Content { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Reason { get; set; }
}
LoginAsync:
public async Task<bool> LoginAsync(string user, string password)
{
var body = new { Username = user, Password = password };
try
{
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response .State == 1;
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
}
PostAsync:
public async Task<object> PostAsync(string url, string body, string authToken = null)
{
var client = new HttpClient();
var content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), content);
var resp = await response.Result.Content.ReadAsStringAsync();
return new ApiResponse
{
Content = resp,
StatusCode = response.Result.StatusCode,
Reason = response.Result.ReasonPhrase
};
}
Assuming a simple method under test like this based on minimal example provided above.
public class ApiService {
private IHttpClient _http;
private string _login_url;
public ApiService(IHttpClient httpClient) {
this._http = httpClient;
}
public async Task<bool> LoginAsync(string user, string password) {
var body = new { Username = user, Password = password };
try {
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response.StatusCode == HttpStatusCode.OK;
} catch (Exception ex) {
//Logger.Error(ex);
return false;
}
}
}
The following test works when configured correctly
[Fact]
public async Task Login_Should_Return_True() { //<-- note the Task and not void
//Arrange
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<string>(), null))
.ReturnsAsync(new ApiResponse() { StatusCode = HttpStatusCode.OK });
var api = new ApiService(mockClient.Object);
//Act
var response = await api.LoginAsync("", "");
//Assert
Assert.IsTrue(response);
}
The above is just for demonstrative purposes only to show that it can work provided the test is configured properly and exercised based on the expected behavior.
Take some time and review the Moq quick start to get a better understanding of how to use the framework.
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>();
}
}