Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing using Shims for ZipFile

I'm attempting to unit test some code that use ZipFile.OpenRead within to extract some XML files from a ZIP (Writing the unit tests with moq)

Is there a way I can replace the call to ZipFile.OpenRead with my own result? I have used shims for similar situations, but I can't figure out what to do in this situation and documentation on shims is pretty sparse.

Here is (part of) the method that needs unit-testing:

    public IEnumerable<ConfigurationViewModel> ExtractXmlFromZip(string fileName)
    {
        var configs = new List<ConfigurationViewModel>();
        using (var archive = ZipFile.OpenRead(fileName))
        {
            foreach (ZipArchiveEntry entry in archive.Entries)
            {
                if (entry.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
                {
                    LoadConfigfromZipArchiveEntry(entry, configs)
                }
            }
        }
        return configs;
   }
like image 429
jimbo Avatar asked Sep 15 '25 17:09

jimbo


2 Answers

There isn't a way to mock a static class like ZipFile using mock, you would either wrap it using an IZipFileWrapper (say)

public IZipFileWrapper
{
     ZipArchive OpenRead(string fileName)
}

public ZipFileWrapper : IZipFileWrapper
{
   public ZipArchive OpenRead(string fileName)
   {
     return ZipFile.OpenRead(fileName)
   }
}

Then the code becomes:

public MyObj
{
   private IZipFileWrapper zipFileWrapper;

   public MyObj(IZipFileWrapper zipFileWrapper)
   {
      this.zipFileWrapper = zipFileWrapper;
   }

   public IEnumerable<ConfigurationViewModel> ExtractXmlFromZip(string fileName)
   {
       var configs = new List<ConfigurationViewModel>();
       // Call the wrapper
       using (var archive = this.zipFileWrapper.OpenRead(fileName))
       {
           foreach (ZipArchiveEntry entry in archive.Entries)
           {
              if (entry.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
              {
                  LoadConfigfromZipArchiveEntry(entry, configs)
              }
           }
       }
       return configs;
    }
}

And a test of

[TestMethod]
public void ExtractXmlFromZip_Test()
{
    var myThing = new MyObj();
    var fileName = "my.zip";
    ZipArchive myZipArchive = CreateTestZipFile(); // Set up your return

    var mockWrapper = new Mock<IZipFileWrapper>();    
    mockWrapper.Setup(m => m.OpenRead(fileName)).Returns(myZipArchive);

        var configs = myThing.ExtractXmlFromZip(fileName);
        // Assert
    }
}

You would probably need to wrap more to get that passing, but hopefully that shows the concept.


(wrote this before I realised it was moq you were asking about and not shims from Microsoft Fakes)
There is an easier way to use Shims that can get to this code - from Microsoft Fakes. The ZipFile class is part of System.IO.Compression.FileSystem, which is in the dll of the same name.

To let us use a ShimZipFile, we need to add a Fakes Assembly:

Note: We need to Fake System.IO.Compression.FileSystem (i.e. the dll) not System.IO.Compression dlll (which is the namespace of ZipFile).

enter image description here

Which should make the following changes to the project:

enter image description here

enter image description here

Then we can use it in a test such as:

[TestMethod]
public void ExtractXmlFromZip_Test()
{
    var myThing = new MyObj();
    var fileName = "my.zip";
    ZipArchive myZipArchive = CreateTestZipFile(); // Set up your return
    using (ShimsContext.Create())
    {
        System.IO.Compression.Fakes.ShimZipFile.OpenReadString = (filename) => myZipArchive;
        var configs = myThing.ExtractXmlFromZip(fileName);
        // Assert
    }
}

There is info on MSDN about the naming conventions for shims that fakes generates.

like image 184
NikolaiDante Avatar answered Sep 18 '25 09:09

NikolaiDante


One of the ways I have recently taken to is using a private delegated member on a class which has the single purpose of wrapping a concrete implementation.

Fortunately, for me, R# makes it easy to create a concrete proxy class with single private member, expose the public members on the wrapped type (through the Delegate Members code generator), then extract the interface to decouple, and provide ways to mock, and verify during testing.

like image 37
Daniel Park Avatar answered Sep 18 '25 09:09

Daniel Park