0

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
}
JD Davis
  • 3,517
  • 4
  • 28
  • 61
  • Possible duplicate of [Unit testing code with a file system dependency](https://stackoverflow.com/questions/129036/unit-testing-code-with-a-file-system-dependency) – Access Denied Nov 15 '18 at 03:18
  • I'm not quite sure if the solution would require a file system dependency since I can inject a byte[] containing the file data rather than fetching it from the disk. – JD Davis Nov 15 '18 at 03:23
  • 1
    If you know that is a possibility @JDDavis, I am not sure why you are asking the question? – mjwills Nov 15 '18 at 03:38
  • @mjwills I was hoping there was some straightforward approach aside from generating the pdf. I don't want to introduce the reference to the Pdf library in that portion of my application (but it's a possibility). – JD Davis Nov 15 '18 at 03:50
  • Do you have any dummy code for the methods you wanted to write unit tests to? – Ashokan Sivapragasam Nov 15 '18 at 04:53
  • @AshokanSivapragasam I've attached the code for the extractor to the question. – JD Davis Nov 15 '18 at 14:52

2 Answers2

2

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.

Alberto Chiesa
  • 7,022
  • 2
  • 26
  • 53
0

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!

Ashokan Sivapragasam
  • 2,033
  • 2
  • 18
  • 39