Pester: not all mocked functions are intercepted - unit-testing

I have a number of modules, including ModuleMain and ModuleSql. There is an interdependence between the modules such that Main-Function in ModuleMain uses 4 functions from ModuleSql:
function Main-Function {
[CmdletBinding(SupportsShouldProcess=$true)]
# The following 4 lines are all wrapped in $PSCmdlet.ShouldProcess() and
# try {} catch {} logic. I have left that out in this post, but I mention
# it in case it is relevant.
$a = New-SqlConnection
$b = Invoke-SqlStoredProc -connection $a
$c = Invoke-SqlQuery -connection $a
Close-SqlConnection -connection $a | Out-Null
return $c
}
I have created a Function-Main1.tests.ps1 file to test Function-Main1. At first I used InModuleScope but then switched to specifying the module per-mock with the -ModuleName parameter.
Import-Module "ModuleMain" -Force
Describe "Main-Function" {
Mock -ModuleName ModuleMain New-SqlConnection {
return $true }
Mock -ModuleName ModuleMain Invoke-SqlStoredProc {
return $true }
Mock -ModuleName ModuleMain Invoke-SqlQuery {
return $true }
Mock -ModuleName ModuleMain Close-SqlConnection {
return $true }
Context "When calling Main-Function with mocked SQL functions" {
It "Calls each SQL function once" {
Assert-MockCalled -Scope Context -ModuleName ModuleMain -CommandName New-SqlConnecion -Times 1 -Exactly
Assert-MockCalled -Scope Context -ModuleName ModuleMain -CommandName Invoke-SqlStoredProc -Times 1 -Exactly
Assert-MockCalled -Scope Context -ModuleName ModuleMain -CommandName Invoke-SqlQuery -Times 1 -Exactly
Assert-MockCalled -Scope Context -ModuleName ModuleMain -CommandName Close-SqlConnecion -Times 1 -Exactly
}
}
}
When I run this test I get the following results:
[-] Calls each SQL function once 223ms
Expected Invoke-SqlStoredProc in module ModuleMain to be called 1 times exactly but was called 0 times
at line: xx in
xx: Assert-MockCalled -Scope Context -ModuleName ModuleMain -CommandName Invoke-SqlStoredProc -Times 1 -Exactly
Note the following:
I did not import ModuleSql, where the -Sql functions are defined, since I am mocking them all anyway.
I have observerd/worked out that I need to set -ModuleName to be the module where Main-Function is defined and not the one where the SQL function (that I am trying to mock) is defined.
I have played around with InModuleScope and -ModuleName, e.g. setting one or other to be ModuleSQL, but mainly that just made things worse.
By playing around, adding Verbose output in the other mocked functions, I have confirmed that New-SqlConnection and Close-SqlConnection are both being intercepted, but Invoke-SqlStoredProc and Invoke-SqlQuery are not.
Probing deeper, I can see that the following exception is being thrown by the Invoke-Sql* (mocked) functions: Error: "Invalid cast from 'System.Boolean' to 'System.Data.SqlClient.SqlConnection'." This is behaviour that I would expect when the real versions of those functions are called, but I am expecting that the mocked versions would ignore parameter types.
Why would Pester intercept only 2 of my 4 functions?

So, the short answer to this question was given in the comments above:
Mocked functions does not ignore parameter types. --PetSerAl
This meant that when I was trying to call the Invoke-Sql* functions, and using the phony $sqlConnection variable (simply set to $true) this was causing an error since the input parameter wasn't the expected data type.
And the solution in my case was to mock the New-SqlConnection function so it returned a [System.Data.SqlCient.SqlConnection] object. As it happens, I also reverted back to using InModuleScope rather than specifying the module on each Mock:
InModuleScope "ModuleMain" {
Describe "MainFunction" {
Mock New-SqlConnection {
New-Object -TypeName System.Data.SqlCient.SqlConnection
}
...
}

Related

Mock Get-ADUser with and without ParameterFilter

I'm pretty new to Pester so please bear with me. We're trying to make tests for a very large script that does some active directory queries and validates data. I've simplified it a bit to the following example:
Dummy.ps1
Param (
[String]$OU,
[String[]]$Groups
)
$AllOUusers = Get-ADUser -Filter * -SearchBase $OU
$GroupMemberUsers = foreach ($G in $Groups) {
Get-ADGroupMember $G -Recursive | Get-ADUser -Properties whenCreated
}
Dummy.Tests.ps1
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
$Params = #{
OU = 'OU=users,DC=contoso,DC=com'
Groups = 'Group1', 'Group2'
}
Describe 'test' {
Mock Get-ADGroupMember {
[PSCustomObject]#{SamAccountName = 'Member1'}
[PSCustomObject]#{SamAccountName = 'Member2'}
}
Mock Get-ADUser {
[PSCustomObject]#{SamAccountName = 'User1'}
[PSCustomObject]#{SamAccountName = 'User2'}
[PSCustomObject]#{SamAccountName = 'User3'}
}
Mock Get-ADUser {
[PSCustomObject]#{SamAccountName = 'User4'}
[PSCustomObject]#{SamAccountName = 'User5'}
} -ParameterFilter {$identity -eq 'User1'}
."$here\$sut" #Params
it 'test 1' {
($AllOUusers | Measure-Object).Count | Should -BeExactly 3
}
it 'test 2' {
($GroupMemberUsers | Measure-Object).Count | Should -BeExactly 2
'User4', 'User5' | Should -BeIn $GroupMemberUsers.SamAccountName
}
}
Error:
Cannot validate argument on parameter 'Identity'.
In the case above we're trying to collect in the first place all user accounts in a specific OU and afterwards all the members of a specific security group. When we have the members we use the Get-ADUser CmdLet again to get more details for these specific members.
The end goal is to have a Mock for the first Get-ADUser that returns all the user accounts, and a second Mock of Get-ADUser that only returns a specific set of user accounts based on group membership (or simulated group membership).
System details
PowerShell 5.1
Pester 4.1.1.
Windows server 2012 R1
AD Module 1.0.0.0.
It seems like the error you're seeing is occurring because the users you create in your Mock of Get-ADGroupMember aren't being mapped to/accepted by the Identity parameter because it accepts pipeline input ByValue and expects an ADUser type object.
You can work around that by using New-MockObject to create the specific object type you need. Here's an alternative option for your Mock of Get-ADGroupMember:
Mock Get-ADGroupMember {
1..2 | ForEach-Object {
$User = New-MockObject -Type Microsoft.ActiveDirectory.Management.ADUser
$User.SamAccountName = "Member$_"
$User
}
}
I've used a ForEach as a quick way to return the 2 users you were returning before, with the value you were setting but as actual ADUser objects.
This doesn't actually make your -ParameterFilter on the second Get-ADUser work however. That seems to be again because the User is an object but you're comparing it to a string, it doesn't evaluate as true.
One possible workaround for this (that I think works somewhat for your specific use case) is not to check for a specific value of $Identity but just to check if it has any value:
Mock Get-ADUser {
[PSCustomObject]#{SamAccountName = 'User4'}
[PSCustomObject]#{SamAccountName = 'User5'}
} -ParameterFilter { $identity }
This causes your 2nd Mock to always be called for the 2nd Get-ADUser in your script as that's the only one that gets piped an Identity value. This isn't exactly what you were trying to achieve, but I figure might help a little.

Pester and testing for enum

How do I test for enum with the Powershell unit test framework Pester?
What I get back from the testee seems to be a string and not my proper enum.
Testresult
The test results in an error. What I got back was Apple and not my enum [FruitType]::Apple.
...
Expected {[FruitEnum]::Apple}, but got {Apple}.
6: $res.TheFruit | Should -Be [FruitEnum]::Apple
...
Fruit.psm1
The Powershell module here makes the enum "public" and exports a method that returns an object with my Fruit enum.
enum FruitEnum{
Apple
}
function Get-Fruit{
return #{
TheFruit = [FruitEnum]::Apple
}
}
Export-ModuleMember -Function Get-Fruit
Fruit.Tests.ps1
The Pester test calls using to get hold of the enum, calls the testee and checks the result.
using module .\Fruit.psm1
Import-Module .\Fruit.psm1 -Force
Describe "Get-Fruit" {
It "returns an enum" {
$res = Get-Fruit
$res.TheFruit | Should -Be [FruitEnum]::Apple
}
}
I have occasionally seen odd things with Pester, and used a trick such as the following to fix them:
($res.TheFruit -eq [FruitEnum]::Apple) | Should Be True
That is, perform the comparison and then check that the result is True, rather than trust that Should will be able to assert that something coming down the pipeline is the Type that you expect it to be.
Another check you could do is to verify the Type of the object:
$res.TheFruit.GetType().Fullname | Should Be "FruitEnum"

Powershell Azure Pester Test

Below is a simple function that just creates a resource group and exports the data. I am trying to learn unit test but I cant seem to figure it out.
Is it possible to give the test mock data? and can I test if the output file would work?
function New-AzureRG{
param([string]$rgName,
[string]$location
)
$getData = New-AzureRmResourceGroup -Name $rgName -location 'WestEurope'
$getData | Export-Csv $location
}
Describe "New-AzureRG" {
Context "Function Exists" {
It "Should return a message" {
$sum = New-AzureRG -rgName testRG -location C:\tst\testsc.csv
($um).Name | Should Be "testRG"
}
}
}
Here is my terrible attempt to make a test using pester. For some reason the test is actually doing it, instead of making it as a test. Im just confused :(.
I do not think your test is actually working. For example in the test $um is not assigned... If you mock the New-AzureRG function in your current test you are testing nothing. I guess you want something like:
Make a function c
Call the function from an other function
Mock the New-AzureRG function in your test
You mock can look something like:
Mock New-AzureRG { return #{Name = "NameRG"} } -ParameterFilter { $Name -eq "NameRG" }

Unit Testing a Class-based DSC resource with Pester

I am having a problem with unit testing a class-based DSC resource. I am trying to Mock a couple of functions in the class and I get a cast error.
PSInvalidCastException: Cannot convert the "bool TestVMExists(string vmPath,
string vmName)" value of type "System.Management.Automation.PSMethod" to type
"System.Management.Automation.ScriptBlock".
My test code is this:
using module 'C:\Program Files\WindowsPowerShell\Modules\xVMWareVM\xVMWareVM.psm1'
$resource = [xVMWareVM]::new()
Describe "Set" {
Context "If the VM does not exist" {
Mock xVMWareVM $resource.TestVMExists {return $false}
Mock xVMWareVM $resource.CreateVM
It "Calls Create VM once" {
Assert-MockCalled $resource.CreateVM -Times 1
}
}
}
Does anyone know how to achieve this?
Thanks in advance
You currently won't be able to mock a class function using Pester. The current workaround is to use Add-Member -MemberType ScriptMethod to replace the function. This means you will not get the mock asserts.
I borrowed this for DockerDsc tests by #bgelens.
Without your class code, I haven't been able to test this, but it should give you the idea along with #bgelens code above.
using module 'C:\Program Files\WindowsPowerShell\Modules\xVMWareVM\xVMWareVM.psm1'
Describe "Set" {
Context "If the VM does not exist" {
$resource = [xVMWareVM]::new()
$global:CreateVmCalled = 0
$resource = $resource |
Add-Member -MemberType ScriptMethod -Name TestVMExists -Value {
return $false
} -Force -PassThru
$resource = $resource |
Add-Member -MemberType ScriptMethod -Name CreateVM -Value {
$global:CreateVmCalled ++
} -Force -PassThru
It "Calls Create VM once" {
$global:CreateVmCalled | should be 1
}
}
}

Pester BoundParams in CallHistory empty

I have a simple that at this point doesn't do anything, I am just getting started with Pester, but my goal is to mock Remove-ADGroupMember and a few other AD powershell commands for testing.
Param(
[string]$computerList = ".\\computers.csv",
[boolean]$isTest = $false
)
function Remove-Groups(){
Remove-ADGroupMember -Identity "dale" -Members "dale","cameron"
}
and my test script
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = "../" + (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"
Describe "UninstallUnused - Remove-Groups" {
It "does something useful" {
Mock Remove-ADGroupMember
Remove-Groups
Assert-MockCalled Remove-ADGroupMember -Exactly 1
Assert-MockCalled Remove-ADGroupMember 1 -ParameterFilter {$Identity -eq "dale" -and $Members -contains "dale"}
}
}
The First Assert-MockCalled line works, but no matter what I try on the second one the tests always fails. After awhile I dug into the Assert-MockCalled funciton with a debugger and it looks like the values of the -Members parameter are getting lost
In the picture above I'm in the Assert-MockCalled function of Mock.ps1 and as you can see the values passed to members are missing. If I only pass 1 value to the Remove-ADGroupMember mock like so: Remove-ADGroupMember -Identity "dale" -Members "dale" the value I see in BoundParams is {} instead of {, } as you see in the screenshot.
Is this an issue with the way I'm doing it or is the problem the way the pester is reading the params etc. from Remove-ADGroupMember?
The Identity nor the Members parameters are of type String, so that's why your assertions do not work.
Identity - Microsoft.ActiveDirectory.Management.ADGroup
Members - Microsoft.ActiveDirectory.Management.ADPrincipal[]
So in order to make the filter work you need to cast to string (the types luckily serialize to the names you need)
function Remove-Groups(){
Remove-ADGroupMember -Identity "dale" -Members "dale","cameron"
}
Describe "UninstallUnused - Remove-Groups" {
It "does something useful" {
Mock Remove-ADGroupMember
Remove-Groups
Assert-MockCalled Remove-ADGroupMember -Exactly 1
Assert-MockCalled Remove-ADGroupMember 1 -ParameterFilter {
Write-Host ( $identity.GetType(),$members.GetType()) ; #otputting the data types
([string[]]$members) -contains "dale" -and ([string]$identity) -eq "dale" }
}
}
Edit:
You could also use the automatic casting to the left parameter, but that makes the code easier to break:
([string[]]$members -contains "dale") -and ("dale" -eq $identity)