First, Shiju Varghese's blog on writing unit tests for an ApiController. Next I used William Hallat's blog on testing a file upload to finish things off.
What I ended up with is pretty neat and easy to use, customized to meet my needs. As usual, I'm posting it here so I don't have to redo the work next time.
The first key to making this work is declaring the controller. Let's just say that our controller has a single object injected, an ILogger (a fairly common practice). Instead of just doing this:
var controller = new ExampleController(_moqLogger.Object);
We want to do this:
1: var controller = new ExampleController(_moqLogger.Object)
2: {
3: Request = new HttpRequestMessage
4: {
5: Content = new ObjectContent(typeof(string), null, new JsonMediaTypeFormatter()),
6: Method = HttpMethod.Post
7: }
8: };
UPDATE: We actually also want to include an HttpConfiguration to prevent another error I was getting later:
1: var request = new HttpRequestMessage
2: {
3: Content = new ObjectContent(typeof(string), null, new JsonMediaTypeFormatter()),
4: Method = HttpMethod.Post
5: };
6: request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
7: var controller = new ExampleController(_moqLogger.Object)
8: {
9: Request = request
8: };
This declares that the request passed to the controller will actually be specified to contain content and a method (POST in this case). Now that we have that, our tests won't fail with the awful (and unhelpful) "Object reference not set to an instance of an object" error you might be seeing.
In this case we've specified that the content will be a string, but we haven't specified any actual content. But as I said before I needed to test whether the content was a file that had been uploaded, then also take certain actions based on that file. To do that I actually needed to fake a file upload in the request itself.
A little bit of setup (this happens before each test run not each test):
1: [TestFixtureSetUp]
2: public void SetUpFixture()
3: {
4: using (var outFile = new StreamWriter(_testFile))
5: {
6: outFile.WriteLine("some test data");
7: }
8: }
The test:
1: [Test]
2: public void DoSomethingShouldReturnAnOkResult()
3: {
4: // arrange
5: var multipartContent = BuildFormDataContent();
6: multipartContent.Add(new StringContent("some value"), "someKey");
7:
8: var controller = new ExampleController(_moqLogger.Object)
9: {
10: Request = new HttpRequestMessage
11: {
12: Content = multipartContent,
13: Method = HttpMethod.Post
14: }
15: };
16:
17: // act
18: var response = controller.DoSomething();
19:
20: // assert
21: Assert.IsInstanceOf<OkResult>(response.Result);
22: }
The method called by the test:
1: private string _testFile = "test.file";
2:
3: private MultipartFormDataContent BuildFormDataContent()
4: {
5: var multipartContent = new MultipartFormDataContent("boundary=---011000010111000001101001");
6:
7: var fileStream = new FileStream(_testFile, FileMode.Open, FileAccess.Read);
8: var streamContent = new StreamContent(fileStream);
9: streamContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
10:
11: multipartContent.Add(streamContent, "TheFormDataKeyForTheFile", _testFile);
12:
13: return multipartContent;
14: }
And finally, the controller action method:
1: [HttpPost]
2: public async Task<IHttpActionResult> DoSomething()
3: {
4: if (!Request.Content.IsMimeMultipartContent("form-data"))
5: {
6: _logger.Information(() => "Unsupported media type");
7: return BadRequest("Unsupported media type");
8: }
9:
10: try
11: {
12: var root = @"C:\";
13: var provider = new MultipartFormDataStreamProvider(root);
14: await Request.Content.ReadAsMultipartAsync(provider);
15:
16: var someValue = provider.FormData.GetValues("someKey").FirstOrDefault();
17:
18: foreach (var file in provider.FileData)
19: {
20: var fileInfo = new FileInfo(file.LocalFileName);
21: // do something with the file here that returns a boolean
22: if(someOtherMethod()){
23: return Ok();
24: }
25:
26: return InternalServerError(new Exception("An error was encountered while processing the request"));
27: }
28:
29: return Ok();
30: }
31: catch (Exception ex)
32: {
33: return InternalServerError(ex);
34: }
35: }
This solved my problem and enabled me to test my action method on my controller.
Doesn't work to declare Request = new HttpRequestMessage (had to be "var request = "), you didn't define what _moqLogger.Object was, and I couldn't add a request model to the Controller like that - it made it lose the context of the Request model (either you get a syntax error on the request line, or you get 'HttpRequestMessage is a type, which is not valid int he given context' if you try & change out "var" for HttpRequestMessage). And what assembly do you have to import to use "HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration"?
ReplyDelete