C# Unit Testing a Custom FileResult That Exports Data into a CSV file Using Streaming in a .NET Core 3.1 MVC App
Tags: csharp, dotnet
Published: 2020-05-29 17:16:50

## Introduction

In the past two months at work, I was tasked with learning C#, as well as creating a web app using the .NET Core 3.1 MVC framework. I wanted to document the most interesting concepts in a series of blog posts.

In my last [blog][1], I demonstrated how to create a custom *FileResult* to export data into a CSV file using streaming in a .NET Core 3.1 MVC web app. In this follow on blog post, I will show you how to unit test the custom *FileResult* and the controller which produces this *FileResult*.

Again, the actual code was more complex; this blog was my attempt to abstract the core concepts into a simple web app using Pusheen the Cat as a fun example!

## Unit Testing Custom FileResult With Streaming in .NET Core 3.1

In my last blog, I had a custom *FileResult* called *PusheenCsvResult*. To set the scene for unit testing, I used the *NUnit* testing framework, along with the *FluentAssertions* and *FluentAssertions.AspNetCore.Mvc* libraries, which provided a clear way to communicate what I was asserting in my test (i.e. what was the expected result). I applied the **Arrange, Act, Assert** structure for this and am still learning the best way to do it!

P.S. Would highly recommend the book [Agile Technical Practices Distilled: A learning journey in technical practices and principles of software design][2].

### What was the goal?

Let's start off with the goal! When we're working with unit testing, it's helpful to define what it is we're checking for. In this unit testing situation, I wanted a way to check that the *PusheenCsvResult*'s method *ExecuteResultAsync* was streaming (writing) the response correctly to the *HttpContext*'s response body.

### How did I go about doing it?

Knowing this, I followed some tips in the [Agile Technical Practices Distilled][2] book to start from the assertion and work backwards (Assert, Act , Arrange). I didn't just magically know what I needed; it took some time to get there.

### Setting it up

I created a *[TestFixture]* for testing *PusheenCsvResult* and within the *[SetUp]*, I defined a *_httpContext*, *_fileDownloadName* and made a fake *_fakeActionContext* object.

The reason I did this was because the *PusheenCsvResult*'s method *ExecuteResultAsync*, took a parameter which was of type *ActionContext*.

Let's recap on my last [blog][1] for a second, the job of *ExecuteResultAsync* is using *StreamWriter* to write to the response body of the *HttpContext* of the *ActionContext* and the stream sits in between the application and in this case the response body. The data is written to the stream (response body) and then from the stream, it then results in the CSV file being produced.

In the case of the unit test scope, I wanted to create a *_fakeActionContext* object as an instance of *ActionContext* and in it's constructor, I set the *HttpContext* as the *_httpContext* I defined earlier in my test *[SetUp]*. This enabled the ability to check what was written to the response body of that *_httpContext*.


```csharp

#Code omitted for brevity

    [TestFixture]
    public class PusheenCsvResultShould
    {
        private PusheenCsvResult _pusheenCsvResult;
        private string _fileDownloadName;
        private string _expectedResponseText;
        private DefaultHttpContext _httpContext;
        private ActionContext _fakeActionContext;

        [SetUp]
        public void Setup()
        {
            _httpContext = new DefaultHttpContext();

            _fileDownloadName = "pusheen.csv";

            _fakeActionContext = new ActionContext()
            {
                HttpContext = _httpContext
            };
        }
        
        [Test]
        public async Task GivenActionContext_ExecuteResultAsync_ShouldWriteLineToHttpResponseBody()
        {
            
            //Arrange
            var data = new List<Pusheen>()
            {
                new Pusheen() { Id = 1, Name = "Pusheen", FavouriteFood = "Ice cream", SuperPower = "Baking delicious cookies" },
                new Pusheen() { Id = 2, Name = "Pusheenosaurus", FavouriteFood = "Leaves", SuperPower = "Roarrrrr!" },
                new Pusheen() { Id = 3, Name = "Pusheenicorn", FavouriteFood = "Butterfly muffins", SuperPower = "Making rainbow poop" }
                
            }.AsQueryable();

            PusheenCsvResult _pusheenCsvResult = new PusheenCsvResult(data, _fileDownloadName);

            _expectedResponseText = System.IO.File.ReadAllText(TestContext.CurrentContext.TestDirectory + @"/TestData/expectedCsv.txt");

            var memoryStream = new MemoryStream();
            _httpContext.Response.Body = memoryStream;

            //Act
            await _pusheenCsvResult.ExecuteResultAsync(_fakeActionContext);
            var streamText = System.Text.Encoding.Default.GetString(memoryStream.ToArray());

            //Assert
            streamText.Should().Be(_expectedResponseText);
        }

    }

```

### Let's hop over to the test

For the *[Test]* itself, I checked that given an *ActionContext*, the method *ExecuteResultAsync* should WriteLine to the HttpContext response body.

I needed a *PusheenCsvResult* for my test, and that took 2 parameters for it's constructor.

1. data (as type *IQueryable<Pusheen>*)
2. fileDownloadName (as type *String*)

I already defined *fileDownloadName* earlier in the *[SetUp]*, so the next step was to make some data for the test scenario. In this case, a *new List<Pusheen>* as *.AsQueryable()* was created and I passed this into *PusheenCsvResult*'s constructor.


Based on this information, I wanted to make a file containing the expected text I would expect to see as *_expectedResponseText*. In my assertion, I checked that the text I got from the stream should match the *_expectedResponseText* for the test to pass.


### Now, this was the tricky bit - how to deal with closed streams?

When I was testing this, I didn't know what was wrong, as the test kept saying that it couldn't access a closed stream. Since I defined the StreamWriter within a *using* block; the stream will be closed once it's done its job. This was not a bad thing and is something I recommend doing in your implementation; but it meant I needed another way to access what was written to the stream for the purpose of the unit testing (in this case, the stream was the HttpContext's response body itself).

I added some comments on the code snippet below to describe what was going on.


```csharp

            // I create a new Memory Stream and set that stream as the Response Body of the _httpContext I'm using in my unit test scope
            var memoryStream = new MemoryStream();
            _httpContext.Response.Body = memoryStream;

            //Act
            // I await and pass the _fakeActionContext to my ExecuteResultAsync method. Reminder that I pointed the HttpContext of ActionContext to the _httpContext I made for testing
            await _pusheenCsvResult.ExecuteResultAsync(_fakeActionContext);

            //I need to make sure that I capture the contents of the memoryStream and store it against the variable streamText which I can access later in my assertion
            var streamText = System.Text.Encoding.Default.GetString(memoryStream.ToArray());

```

## Unit testing the Controller

The controller was a bit more straightforward. I used *Moq* to mock the *PusheenService* and its method *GetAllPusheens()* to return some data.

```csharp

_mockPusheenService.Setup(p => p.GetAllPusheens()).Returns(data);

```

Here, I tested that *ExportCsv* on the *PusheenController* returned the result of type *PusheenCsvResult* and that the *fileDownloadName* and *contentType* were correct.

```csharp

#The rest of the code has been omitted for brevity! :)

namespace PusheenCustomExportCsv.Tests.Controllers
{
    [TestFixture]
    public class PusheenControllerShould
    {
        private PusheenController _controller;
        private Mock<IPusheenService> _mockPusheenService;
        private Mock<IConfiguration> _mockConfig;
        private DbContextOptions<PusheenCustomExportCsvContext> _testDbOptions;
        private PusheenCustomExportCsvContext _testDbContext;
        
        [SetUp]
        public void Setup()
        {
            _mockPusheenService = new Mock<IPusheenService>();
            _controller = new PusheenController(_mockPusheenService.Object);
        }
        
        [Test]
        public void ExportCsv_Returns_CsvResult()
        {
            //Arrange
            var data = new List<Pusheen>()
            {
                new Pusheen() { Id = 1, Name = "Pusheen", FavouriteFood = "Ice cream", SuperPower = "Baking delicious cookies" },
                new Pusheen() { Id = 2, Name = "Pusheenosaurus", FavouriteFood = "Leaves", SuperPower = "Roarrrrr!" },
                new Pusheen() { Id = 3, Name = "Pusheenicorn", FavouriteFood = "Butterfly muffins", SuperPower = "Making rainbow poop" }
                
            }.AsQueryable();

            _mockPusheenService.Setup(p => p.GetAllPusheens()).Returns(data);

            //Act
            var result = _controller.ExportCsv();

            //Assert
            result.Should().BeOfType(typeof(PusheenCsvResult));
            result.FileDownloadName.Should().Be("pusheen.csv");
            result.ContentType.Should().Be("text/csv");

        }

        
    }
}

```


## Final Thoughts

I hope you find this post useful and being part of my coding journey! Thank you for reading my blog! :)


## Link to Github Repo

You can find the link to my Github repo with the simple web app example containing the custom fileresult and test project [here][3].

[1]: https://kimdiep.github.io/kimdiep-gatsby-blog/posts/custom-csvresult-fileresult-dotnetcore-mvc/
[2]: https://www.amazon.co.uk/Agile-Technical-Practices-Distilled-principles/dp/1838980849/ref=pd_lpo_14_t_0/262-2345187-8535340?_encoding=UTF8&pd_rd_i=1838980849&pd_rd_r=3b0ea091-114f-4741-bf68-141b316c4e70&pd_rd_w=j3FEq&pd_rd_wg=QeWz3&pf_rd_p=7b8e3b03-1439-4489-abd4-4a138cf4eca6&pf_rd_r=BQAAG5YRVP90CYHGFSCH&psc=1&refRID=BQAAG5YRVP90CYHGFSCH
[3]: https://github.com/kimdiep/pusheen-custom-fileresult-csv-export-aspnetcore