Search code examples
unit-testingpowershellazuremockingpester

Pester mock error PSInvalidCastException on Azure cmdlet call


I need to "Pester-test" 2 Azure cmdlets, Get-AzureNetworkSecurityGroup and Set-AzureNetworkSecurityRule from a PowerShell function inside a module, that looks like following:

  $nsg = Get-AzureNetworkSecurityGroup -Name $NsgName
  Set-AzureNetworkSecurityRule -Name $NsgRule.name `
      -Type Outbound `
      # ... other properties here ... 
      -NetworkSecurityGroup $nsg |
    Format-List -Property Name,Location,Label

The parameters $NsgName, $NsgRule are not so important, the problem is I receive errors when mocking Set-AzureNetworkSecurityRule like:

Mock Get-AzureNetworkSecurityGroup { return [PSCustomObject] @{ Name='Any' } }
Mock Set-AzureNetworkSecurityRule
Mock Format-List

The error says:

 [-] Error occurred in Describe block 100ms
   PSInvalidCastException: Cannot convert the "@{Name=Any}" value of type "System.Management.Automation.PSCustomObject" to type "Microsoft.WindowsAzure.Commands.ServiceManagement.Network.NetworkSecurityGroup.Model.INetworkSecurityGroup".
   ArgumentTransformationMetadataException: Cannot convert the "@{Name=Any}" value of type "System.Management.Automation.PSCustomObject" to type "Microsoft.WindowsAzure.Commands.ServiceManagement.Network.NetworkSecurityGroup.Model.INetworkSecurityGroup".
   ParameterBindingArgumentTransformationException: Cannot process argument transformation on parameter 'NetworkSecurityGroup'. Cannot convert the "@{Name=Any}" value of type "System.Management.Automation.PSCustomObject" to type "Microsoft.WindowsAzure.Commands.ServiceManagement.Network.NetworkSecurityGroup.Model.INetworkSecurityGroup".

It's quite clear what's happening, the issue is I don't know how to mock an object of type INetworkSecurityGroup. My expectation was initially to be no problem if mocking both Azure cmdlets.

I've also tried mocking Set-AzureNetworkSecurityRule using -MockWith:

Mock Set-AzureNetworkSecurityRule -MockWith {@{NetworkSecurityGroup='test-stuff'}}

with no luck.

Can anyone point me to the right direction ?
Thanks in advance

UPDATE with full Describe statement

First try

  $environmentConfig = (Get-AzureEnvironmentConfig "Staging")

  Describe 'Set-NetworkSecurityRuleFromObject' {
    $role = ($environmentConfig.roles | Where { $_.role_name -eq 'Web'})
    $nsgName = Get-NetworkSecurityGroupName -EnvironmentConfig $environmentConfig -Role $role
    $rule = $role.nsg_rules.outbound[0]

    Mock Get-AzureNetworkSecurityGroup
    Mock Set-AzureNetworkSecurityRule

    Set-NetworkSecurityRuleFromObject -NsgName -$nsgName -NsgRule $rule -NsgRuleType "Outbound"

    It 'Should call mocked functions at least once' {
      Assert-MockCalled Get-AzureNetworkSecurityGroup -Times 1 -Scope Describe
      Assert-MockCalled Set-AzureNetworkSecurityRule -Times 1 -Scope Describe
    }
  }

Associated PS module function call:

  Get-AzureNetworkSecurityGroup -Name $NsgName |
    Set-AzureNetworkSecurityRule -Name $NsgRule.name `
      -Type $NsgRuleType `
      # More parameter initialization here ...
      -Protocol $NsgRule.protocol |
    Format-List -Property Name,Location,Label

Second try, another implementation of PS function that didn't work:

  $nsg = Get-AzureNetworkSecurityGroup -Name $NsgName
  $nsg |
    Set-AzureNetworkSecurityRule -Name $NsgRule.name `
      -Type $NsgRuleType `
      # More parameter initialization here ...
      -Protocol $NsgRule.protocol |
    Format-List -Property Name,Location,Label

Third try

  Describe 'Set-NetworkSecurityRuleFromObject' {
    [ ... ]
    Mock Get-AzureNetworkSecurityGroup { return [PSCustomObject] @{ Name='Any' } }
    Mock Set-AzureNetworkSecurityRule

    Set-NetworkSecurityRuleFromObject -NsgName -$nsgName -NsgRule $rule -NsgRuleType "Outbound"

    It 'Should call mocked functions at least once' {
      Assert-MockCalled Get-AzureNetworkSecurityGroup -Times 1 -Scope Describe
      Assert-MockCalled Set-AzureNetworkSecurityRule -Times 1 -Scope Describe
    }
  }

Solution

  • This is still one of the most challenging things to do with Pester. You need to mock Get-AzureNetworkSecurityGroup in such a way that it returns a valid object to be passed to Set-AzureNetworkSecurityGroup later, and figuring out just how to do that can sometimes be tricky. In this specific case, it's not too bad:

    Mock Get-AzureNetworkSecurityGroup {
        return New-Object Microsoft.WindowsAzure.Commands.ServiceManagement.Network.NetworkSecurityGroup.Model.SimpleNetworkSecurityGroup('Any', 'someLocation', 'someLabel')
    }
    

    Since this function parameter is an interface type, you could also write a quick class in C# (or in PowerShell v5) which implements that interface; sometimes this might be more desirable, if the actual class starts doing "stuff" as soon as you create an instance. However, this SimpleNetworkSecurityGroup class doesn't really do anything other than hold data, and it's safe to use as-is. (Verified with ILSpy.)