4

I have a PowerShell script for setting up a website in IIS. It includes the following function:

function Set-IisWebsiteBinding (
    [Parameter(Mandatory = $true)]
    [string]$SiteName, 
    
    [Parameter(Mandatory = $true)]
    [Microsoft.Web.Administration.Site]$Website,
    
    [Parameter(Mandatory = $true)]
    [array]$WebsiteBindingSettings
)
{
    # Set up website bindings ...
}

I'm trying to write tests for the function in Pester. I'd like to create a dummy $Website object to pass to the function under test. The problem is that the Microsoft.Web.Administration.Site type has no constructor. How can I create an object of type Microsoft.Web.Administration.Site in PowerShell if it has no constructor?

I could create one via IISServerManager.Sites.CreateElement() but I'd like to avoid having to depend on using a real IISServerManager. Alternatively, I could get rid of the type coercion for the function parameter $Website, to allow the test code to pass in a hashtable instead of a Microsoft.Web.Administration.Site object. However, I'd prefer to keep the type coercion so it's obvious to future maintainers of the script what the type of the $Website object is.

Is there any way around this lack of a constructor without using a real IISServerManager or removing the type coercion?

7
  • Having [Microsoft.Web.Administration.Site] as a parameter type will force you to at least have the module when testing, otherwise your function would be throwing a type not found exception. In which case, wouldn't it be better to test with a real Site instance ? Commented Apr 28 at 11:45
  • You could make a copy of applicationHost.config to a temp path and use this ctor to instantiate a custom ServerManager instance. Make sure to update default paths in the root config Commented Apr 28 at 12:02
  • Creating a custom class MockSiteClass to mimic the Microsoft.Web.Administration.Site class is a common practice in unit testing. This allows you to simulate the behavior of complex objects without needing the actual dependencies. Pester is a powerful testing framework for PowerShell, and it supports mocking, which is essential for isolating the code under test. By using a mock class, you can keep the type coercion in your function parameters, which helps maintain clarity about the expected types and improves code readability for future maintainers. Commented Apr 28 at 15:56
  • 3
    You can do $site = New-MockObject -Type ([Microsoft.Web.Administration.Site]) however that will require you to have the module pre-loaded Commented Apr 28 at 22:16
  • 2
    @SantiagoSquarzon, New-MockObject is absolutely perfect for my use case. I have no problem loading Microsoft.Web.Administration.dll for testing. If you'd like to make it an answer I'll accept it. Commented Apr 29 at 9:52

1 Answer 1

3

Pester provides an easy option to create an object for a type that doesn't offer a public constructor via New-MockObject:

$instance = New-MockObject -Type ([Microsoft.Web.Administration.Site])

Alternatively you can use the same API used by Pester to create it, FormatterServices.GetUninitializedObject(Type).

In both cases as noted in comments and still worth adding is that you will need to have the IISAdministration Module or its assembly loaded.

Add-Type '
public class MyType
{
    private MyType() { }
}'

$instance = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([MyType])
$instance.GetType()

#    Namespace: 
# 
# Access        Modifiers           Name
# ------        ---------           ----
# public        class               MyType : object

If you're curious, the method essentially uses an internal API in RuntimeType: RuntimeHelpers.CoreCLR.cs#L330-L345. Not sure what it uses for .NET Framework, code is closed source.

$methodInfo = [MyType].GetType().GetMethod(
    'GetUninitializedObject',
    [System.Reflection.BindingFlags] 'NonPublic, Instance')

$methodInfo.Invoke([MyType], @())
Sign up to request clarification or add additional context in comments.

3 Comments

A note for anyone wanting to use GetUninitializedObject: There's no overload that allows you to add property values or method bodies. Instead, after executing GetUninitializedObject to instantiate an object, you can add them via the PSObject property. Example of adding a property: $instance.PSObject.Properties.Add([PSNoteProperty]::new('MyProperty', 'Property value')). Example of adding a method: $instance.PSObject.Methods.Add([PSScriptMethod]::new('MyMethod', { Write-Host 'Hello world!' })).
@SimonElms yep, that's pretty much how New-MockObject does it
Yeah, when I saw that GetUninitializedObject didn't have an overload to set property values or method bodies I had a look at the Pester source code to see how they did it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.