JD Davis
JD Davis

Reputation: 3720

Unit Testing class that requires file data?

I'm working on expanding our unit test suite, and I've come across a specific class that I'm attempting to figure out how to mock. I have a method that accepts a byte[] array as a parameter. In a perfect world, this byte array will always be a PDF file that contains a form of some sort. It then extracts all of the form fields from that pdf and returns them.

How can I potentially mock up logic that is dependent on file data? My only real ideas are to include the pdf in the project and use IO to read the test file, or to attempt to generate a pdf form on the fly and then extract those fields.

Here is the code for the PDF extractor:

public class PdfFormExtractor : IDisposable
{
    private readonly PdfReader _pdfReader;
    private readonly MemoryStream _newPdf;
    private readonly PdfStamper _pdfStamper;

    public PdfFormExtractor(byte[] pdf)
    {
        _pdfReader = new PdfReader(pdf);
        _newPdf = new MemoryStream();
        _pdfStamper = new PdfStamper(_pdfReader, _newPdf);
    }

    public FormDto ExtractForm()
    {
        var pdfFormFields = _pdfStamper.AcroFields;
        var form = new FormDto()
        {
            Fields = pdfFormFields.Fields.Select(n => new FormFieldDto
            {
                Name = n.Key,
                Label = n.Key
            }).ToList()
        };

        return form;
    }

    #region IDisposable Support
    // disposable implementation
    #endregion
}

Upvotes: 0

Views: 1862

Answers (2)

Alberto Chiesa
Alberto Chiesa

Reputation: 7350

Use Resource files.

In Visual Studio, just create a resource file in your test project to contain all the files you want to use in your tests. Open the resx and you will see the usual list of strings. But you're not limited to strings: you can select "Files" in the top-left dropdown and then drag and drop files INTO the resx file.
When you do, pay attention to the pasted file properties: you can select to interpret the file as binary (a byte[] is exposed, as in your use case) or text (with encoding, which exposes a string).

Then, in your test you can just reference the strongly typed Resource object and the strongly typed byte[] with the contents of your test file.

This strategy has a lot of applications when testing complex scenarios, especially when paired with a smart enough serializer/deserializer (like Json.NET).

You can serialize any complex data structure as Json, then in your tests reference it as a string (exposed directly by the Resource file's class), deserialize it with a simple JsonConvert.DeserializeObject and run your test on the business logic directly.

Upvotes: 2

Ashokan Sivapragasam
Ashokan Sivapragasam

Reputation: 2189

You can use Microsoft.Fakes to generate fake assembly for your *.dll. With Fakes, we can bend the outcome of any properties, methods,..

I faked Sqlconnection class which usually is hardened for mocking.

  1. Right-click on your assembly (in my case, System.Data)
  2. Create fakes assembly
  3. It creates shims & stubs
  4. We need to add scope by using (ShimsContext.Create()). Everything inside the scope will behave as you proposed.

    public void ExtractFormTest()
    {
    using (ShimsContext.Create())
    {
        #region FakeIt
        System.Data.SqlClient.Fakes.ShimSqlConnection.AllInstances.Open = (SqlConnection sqlConnection) =>
        {
            Console.WriteLine("Opened a session with Virtual Sql Server");
        };
    
        System.Data.SqlClient.Fakes.ShimSqlConnection.AllInstances.Close = (SqlConnection sqlConnection) =>
        {
            Console.WriteLine("Closed the session with Virtual Sql Server");
        };
    
        System.Data.SqlClient.Fakes.ShimSqlCommand.AllInstances.ExecuteNonQuery = (SqlCommand sqlCommand) =>
        {
            if (sqlCommand.CommandText.ToLower().Contains("truncate table"))
            {
                Console.WriteLine("Ran " + sqlCommand.CommandText + " at Virtual Sql Server");
                return 1;
            }
    
            return 0;
        };
    
        System.Data.SqlClient.Fakes.ShimSqlBulkCopy.AllInstances.WriteToServerDataTable = (SqlBulkCopy sqlBulkCopy, DataTable datatable) =>
        {
            Console.WriteLine("Written #" + datatable.Rows.Count + " records to Virtual Sql Server");
        };
    
        System.Data.Common.Fakes.ShimDbDataAdapter.AllInstances.FillDataSet = (DbDataAdapter dbDataAdapter, DataSet dataSet) =>
        {
            var _dataSet = new DataSet();
            var _dataTable = DataTableHelper.LoadFlatfileIntoDataTable(Path.Combine(dailyEmailFlatfilesDirectory, "Flatfile.txt"), flatfileDelimiter, flatfileDataTableFields, regexPatternMdmValidEmail, traceWriter);
            if (dbDataAdapter.SelectCommand.CommandText.Equals(mdmSqlStorProcForSpFlatfileData))
            {
                while (_dataTable.Rows.Count > 1000)
                    _dataTable.Rows.RemoveAt(0);
            }
            else if (dbDataAdapter.SelectCommand.CommandText.Equals(mdmSqlStorProcForStFlatfileData))
            {
                while (_dataTable.Rows.Count > 72)
                    _dataTable.Rows.RemoveAt(0);
            }
    
            dataSet.Tables.Add(_dataTable);
            dataSet = _dataSet;
            return 1;
        };
        #endregion
    
        #region Act
        FormDto formDto = ExtractForm();
        #endregion
    
        #region Assert
        // Upto the scope of your method and acceptance criteria
        #endregion
    }
    

    }

Hope this helps!

Upvotes: 0

Related Questions