Mocking the VBA Editor: Moq an IEnumerable that Works with Linq

It’s been a rough morning here. I’ve just spent six hours trying to properly create a mock for the VBProject interface. There’s very little information out there about this, so I thought I’d take a moment to jot this down and save someone else the headache. For all the grief this gave me, the solution is amazing simple.

The Problem

Rubberduck interacts with the VBE a lot. So, in order to automate our tests, we need to mock up the editor and many of the bits and pieces inside of it. The problem is that this is COM, so there is no generic IEnumerable. We only get the old plain Jane IEnumerable, but because Linq is awesome, we have a lot of places in our code that cast from a regular IEnumerable to the generic version. Like this.

var projects =  VBE.VBProjects.Cast<VBProject>()
    .Where(p => p.Name == "VBAProject");

Which is great. It really simplifies the code. The problem is in the mocking.

The intuitive way to create this mock is to create a list of mock projects and return the enumerator from the List. Although, there are other ways of setting up the Enumerator.

var project = new Mock<VBProject>();
project.SetUp(p => p.Name, "VBAProject");

var projectList = new List<VBProject>() { project.Object };

var projects = new Mock<VBProjects>();
projects.SetUp(p => p.GetEnumerator()).Returns(projectList.GetEnumerator());

var vbe = new Mock<VBE>();
vbe.SetupGet(v = v.VBProjects).Returns(projects.Object);

Looks good, right? We can iterate over the code in a foreach loop.

public void SomeCodeUnderTest(VBE vbe)
{
    foreach(var project in vbe.VBProjects)
    {
        Console.WriteLine(project.Name);
    }
}

So what’s the problem then?!

The problem is this blows up when you cast it to a generic IEnumerable. Remember this code?

var projects =  VBE.VBProjects.Cast<VBProject>()
    .Where(p => p.Name == "VBAProject");

It runs fine, but it returns nothing, so our variable is null. The second you try to use it things blow up with a NullReferenceException.

I’ll spare you the six hours of my agony trying to figure out why this is and skip to the solution. It’s blowing up because the we’ve not specified an implementation for IEnumerable’s GetEnumerator method. We’ve only specified it for VBProjects. To fix this, we need to tell the mock to explicitly implement both interfaces and setup a return value for  both of them. If you only specify one or the other, it will work for iterating or Linq, but not both.

Luckily, Moq makes this really easy (once you understand why this is happening at least). We just use the As method to setup the IEnumerable interface on the same mock object.

var projects = new Mock<VBProjects>();
projects.SetUp(p => p.GetEnumerator()).Returns(projectList.GetEnumerator);
projects.As<IEnumerable>().SetUp(p => p.GetEnumerator()).Returns(projectList.GetEnumerator);

And there you go. This will work for Linq and the regular ol’ for each loop.

Until next time,
Semper Cogitet

Advertisements

, , , , , , , , , ,

  1. #1 by holger leichsenring on June 15, 2015 - 6:31 pm

    You anyway shouldn’t use Linq when using Interop objects. The office application (Excel, Word, ..) will not be quitable, you would have to kill the process. Please refer to this http://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects

    Like

    • #2 by Christopher J. McClellan on June 15, 2015 - 6:50 pm

      Good point, but it’s not a problem for my project as we’re not invoking an outside COM object, we’re running from within the context of the VBIDE as an add-in itself. It’s all in process to the host application. When the host shuts down, it tears down our app with it. (Obviously, with a little help from us.) Never have I had the problem of the Excel task hanging in memory.

      Like

  2. #4 by Christopher J. McClellan on June 15, 2015 - 7:10 pm

    We also apply this pattern in our project, it could be why we’re not having trouble. It’s from the same SO question you referenced. http://stackoverflow.com/a/159419/3198973 If you’re really concerned about making the memory management easier, I’d look into http://netoffice.codeplex.com/ The assemblies are Office version independent and all of the objects implement IDisposable, making things significantly easier. It’s a great project, but be aware that even though they claim a 1 to 1 mapping with Office, they didn’t properly map methods that need to take in `ref` args. as for whether mocking like this is feasible for you, I don’t know. Maybe prototype something and see if you run into any issues. Thanks btw. We’re really proud of our little duck. =;)-

    Like

  3. #5 by holger leichsenring on June 15, 2015 - 7:30 pm

    I already considered netoffice. For testing purposes it is may be an attrative idea I will have a follow up with but for our product (automating Excel for being scalable and performant) it isn’t. 10% less speed is considerable. Didn’t know the fact about the ref, It is most often hard enough to understand office interop, guess you guys also had some “adventures”. Having an additional layer in between does not necessarily make anything simpler. 😉 Thanks anyway!

    Like

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: