PowerShell unit testing: mocking constructors

I started using PowerShell about a year ago (my new team uses it for both build and deployment scripts), and I keep meaning to start blogging about it.

Today’s tip: mocking constructors.

In most languages, you can’t write a unit test that intercepts constructor calls. If you have a method like this:

public void OpenBrowser() {
    var driver = new FirefoxDriver();
    // ...

then your method is tightly coupled to the FirefoxDriver class, and (without refactoring) there’s no way for your tests to call that method without creating a new instance of FirefoxDriver, and now everything is fraught with peril. This kind of tight coupling has been a problem since forever, and it’s the reason why we have the Factory pattern.

(Yes, you can use something like Microsoft Shims or TypeMock Isolator to intercept constructor calls, but those work by going outside the language and sinking their hooks deep into the .NET runtime. And you still have to return a FirefoxDriver instance; you can’t substitute something else.)

But in PowerShell, as I recently discovered, you don’t need the Factory pattern. The way to create .NET objects is to call the New-Object function. And Pester, the PowerShell unit-testing framework, can mock any function. Including New-Object.

Now, in C# code, I’m all about refactoring the code to use a factory (or a Func<T>, or dependency injection). But PowerShell is a different beast with different idioms, and I’ve found cases where mocking New-Object makes really good sense.

Here’s the basic template I work off of:

Mock New-Object {
    [PSCustomObject] @{
        Property1 = "value1"
        Property2 = 42

This tells Pester to replace the New-Object function with my own script block, which uses the [PSCustomObject] syntactic sugar to return a new object with two properties, Property1 and Property2.

The script block that you pass to Mock can also access the $TypeName and $ArgumentList parameters that were passed to New-Object, so you can do things like returning fakes for some classes but falling back to the original New-Object cmdlet for all other classes:

Mock New-Object {
    $Type = Invoke-Expression "[$TypeName]"
    if ([OpenQA.Selenium.Remote.RemoteWebDriver].IsAssignableFrom($Type)) {
        [PSCustomObject] @{}
    } else {
        & (Get-Command New-Object -CommandType Cmdlet) @Args

But I’d been using this technique for a while before I had to get that fancy. Mostly in PowerShell you don’t use New-Object a lot; arrays and hash tables and [PSCustomObject]s have their own dedicated syntax and don’t need to go through New-Object (which is fine since they don’t have any side effects you’d need to worry about mocking).