DSC Class Resource & Pester

Hello,
I am working on writing some Pester Tests around a DSC Class Resource to get published in the Gallery. Running into some questions around Pester. Disclaimer that this is the first time ever doing unit tests so it has been an adventure in learning. Doing this for the first time on a Class resources may not have been a wise choice for learning but what better way to learn, right?

Anyway, in the resource there are helper methods and one thing that I am running into right now is some of the helper methods call other helpers to resolve needed data. We tried to keep the helpers as small as possible and only do one function. Maybe this was not the route to go as it seems to make the testing more difficult.

Right now I am Mocking out all of the items needed for the specific method I am testing and the subsequent helper methods that are called from the method being tested, which so far is working but the test code starts to get a little messy. Doing the testing this way seems to test those helper methods indirectly and our plan was to test all of them directly as well.

I think I am just looking for some ideas/best practices around testing this resource. i.e. Why the hell did you do this here? You can do it better by doing this. I have watched Adam Betram’s MVA course on Pester a couple of times, which was awesome content, and I think I am very close to buying the book which I think will help us progress down the Pester path. Maybe Don and Adam could do a leanpub bundle with Pester and DSC books. (wink)

Here is our DSC Resource

Here is what I have for the test. Not in the Repo yet as we are still working on them:

using module ..\WazuhOSSecDSC.psm1

Describe 'Testing the Class based DSC resource WazuhAgentInstall' {

    Context 'Testing the Get() Method' {

        $myObject = New-Object -TypeName WazuhAgentInstall
        Mock Get-Service -ModuleName 'WazuhOSSecDSC' {
            return @{ Name = "OssecSvc"; Status = "Running" }
        }

        Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
            return @{ DisplayName = 'Wazuh Agent 2.1.1'; DisplayVersion = "2.1.1" }
        }

        It 'Return type of the Get() Method should be "WazuhAgentInstall"' {
            ($myObject.Get()).Gettype() | Should be 'WazuhAgentInstall'
        }

        It 'Get-Service and Get-ItemProperty should Return $true and Installed' {
            $results = $myObject.Get()
            $results.Installed | Should be "Present"
            $results.InstalledVersion | Should be "2.1.1"
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Scope it -Times 1 -Exactly
            Assert-MockCalled Get-Service -ModuleName 'WazuhOSSecDSC' -Scope it -Times 1 -Exactly
        }

        It 'Should return Absent if Service not installed' {
            Mock Get-Service { } -ModuleName 'WazuhOSSecDSC'
            $results = $myObject.Get()
            $results.installed | Should be "Absent"
            Assert-MockCalled Get-Service -ModuleName 'WazuhOSSecDsc' -Times 1 -Scope it -Exactly
        }

        It 'Should return Absent if Service Installed and Package not found' {
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ }
            }
            $results = $myObject.Get()
            $results.installed | Should be "Absent"
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDsc' -Times 1 -Scope it -Exactly
        }
    }

    Context 'Testing the Test() Method' {
        $myObject = New-Object -TypeName WazuhAgentInstall
        Mock Get-Service -ModuleName 'WazuhOSSecDSC' {
            return @{ Name = "OssecSvc"; Status = "Running" }
        }

        Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
            return @{ DisplayName = 'Wazuh Agent 2.1.1'; DisplayVersion = "2.1.1" }
        }

        It 'Should return $false if Ensure is Present and Agent Not Installed' {
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ }
            }
            $results = $myObject.Test()
            $results | Should be $false
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDsc' -Times 1 -Scope it -Exactly
        }

        It 'Should return $false if Ensure is Present and is a Version Upgrade' {
            # Mock Get-ItemProperty for VersionUpgrade() Need to return .VersionInfo.FileVersion
            #   and .VersionInfo.CompanyName

            $results = $myObject.Test()
            $results | Should be $false
            Assert-MockCalled
        }

        It 'Should return $true if Ensure = Present and VersionUpgrade = $false' {

        }


     }

    Context 'Testing the Set() Method' {
        $results = "Boo"
    }

    Context 'Testing the ValidateInstallerPath() Method' {
        $results = "Boo"
    }

    Context 'Testing the VersionUpgrade() Method' {
        $results = "Boo"
    }

    Context 'Testing the WazuhInstaller() Method' {
        $results = "Boo"
    }
}

Just following up with this. We plowed through and was able to get it all working. I think at the end of it we realized there are some features in Pester that we might have been able to use to help us along with mocking our helper methods.(New-MockObject). In the end we were able to get 100% coverage on one of our resources in the Class. Armed with our newfound knowledge we are going to tackle the other resource. Here is the test file for the WazuhAgentInstall resource we ended up with. Took quite a bit of debugging to make sure the paths were covered and items were getting mocked the way we expected but over all a very good learning experience.

using module ..\WazuhOSSecDSC.psm1

Describe 'Testing the Class based DSC resource WazuhAgentInstall' {

    Context 'Get() Method' {
        $myObject = [WazuhAgentInstall]::new()
        Mock Get-Service -ModuleName 'WazuhOSSecDSC' {
            return @{ Name = "OssecSvc"; Status = "Running" }
        }
        Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
            return @{ DisplayName = 'Wazuh Agent 2.1.1'; DisplayVersion = "2.1.1" }
        }

        It 'Return type of the Get() Method should be "WazuhAgentInstall"' {
            ($myObject.Get()).Gettype() | Should be 'WazuhAgentInstall'
        }

        It 'Get-Service and Get-ItemProperty should Return $true and Installed' {
            $results = $myObject.Get()
            $results.Installed | Should be "Present"
            $results.InstalledVersion | Should be "2.1.1"
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Scope it -Times 1 -Exactly
            Assert-MockCalled Get-Service -ModuleName 'WazuhOSSecDSC' -Scope it -Times 1 -Exactly
        }

        It 'Should return Absent if Service not installed' {
            Mock Get-Service { } -ModuleName 'WazuhOSSecDSC'
            $results = $myObject.Get()
            $results.installed | Should be "Absent"
            Assert-MockCalled Get-Service -ModuleName 'WazuhOSSecDsc' -Times 1 -Scope it -Exactly
        }

        It 'Should return Absent if Service Installed and Package not found' {
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ }
            }
            $results = $myObject.Get()
            $results.installed | Should be "Absent"
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDsc' -Times 1 -Scope it -Exactly
        }
    }

    Context 'Test() Method' {
        #Instantiate Object
        $myObject = [WazuhAgentInstall]::new()
        $myObject.InstallerPath = "C:\Software\Installer.exe"
        It 'Should return $false if Ensure is Present and Agent Not Installed' {
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ }
            }
            $myObject.Ensure = "Present"
            $results = $myObject.Test()
            $results | Should be $false
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDsc' -Times 1 -Scope it -Exactly
        }

        It 'Should return $false if Ensure is Absent and Agent Is Installed' {
            Mock Get-Service -ModuleName 'WazuhOSSecDSC' {
                return @{ Name = "OssecSvc"; Status = "Running" }
            }
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ DisplayName = 'Wazuh Agent 2.1.1'; DisplayVersion = "2.1.1" }
            }
            $myObject.Ensure = "Absent"
            $results = $myObject.Test()
            $results | Should be $false
            Assert-MockCalled Get-Service -ModuleName 'WazuhOSSecDsc' -Times 1 -Scope it -Exactly
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDsc' -Times 1 -Scope it -Exactly
        }

        It 'Should return $true if Ensure = Present and VersionUpgrade = $false' {
            # Mock Get-ItemProperty for VersionUpgrade() Need to return .VersionInfo.FileVersion
            #   and .VersionInfo.CompanyName
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'C:\Software\Installer.exe'} {
                return @{
                    VersionInfo = @{
                        FileVersion = "2.1.1";
                        CompanyName = "Wazuh"
                    }
                }
            }
            # Mock Get-ItemProperty for the Get() Method
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ DisplayName = 'Wazuh Agent 2.1.1'; DisplayVersion = "2.1.1" }
            }
            # Mock Test-Path for ValidateInstallerPath()
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\Installer.exe"} {
                return {$true}
            }
            $myObject.Ensure = "Present"
            $results = $myObject.Test()
            $results | Should be $true
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Times 2 -Exactly -Scope it
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }

        It 'Should return $false if Ensure is Present and is a Version Upgrade' {
            # Mock Get-ItemProperty for VersionUpgrade() Need to return .VersionInfo.FileVersion
            #   and .VersionInfo.CompanyName
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'C:\Software\Installer.exe'} {
                return @{
                    VersionInfo = @{
                        FileVersion = "2.2.1";
                        CompanyName = "Wazuh"
                    }
                }
            }
            # Mock Get-ItemProperty for the Get() Method
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ DisplayName = 'Wazuh Agent 2.1.1'; DisplayVersion = "2.1.1" }
            }
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\Installer.exe"} {
                return {$true}
            }
            $myObject.Ensure = "Present"
            $results = $myObject.Test()
            $results | Should be $false
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Times 2 -Exactly -Scope it
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }

        It 'Should return $true if Ensure is Absent and is a Version Upgrade' {
            # Mock Get-ItemProperty for VersionUpgrade() Need to return .VersionInfo.FileVersion
            #   and .VersionInfo.CompanyName
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'C:\Software\Installer.exe'} {
                return @{
                    VersionInfo = @{
                        FileVersion = "2.2.1";
                        CompanyName = "Wazuh"
                    }
                }
            }
            # Mock Get-ItemProperty for the Get() Method
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ DisplayName = 'Wazuh Agent 2.1.1'; DisplayVersion = "2.1.1" }
            }
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\Installer.exe"} {
                return {$true}
            }
            $myObject.Ensure = "Present"
            $results = $myObject.Test()
            $results | Should be $false
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Times 2 -Exactly -Scope it
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }
    }

    Context 'Testing the Set() Method' {
        #Instantiate Object
        $myObject = [WazuhAgentInstall]::new()
        $myObject.InstallerPath = "C:\Software\Installer.exe"
        It 'Should execute the installer if Ensure = Present' {
            $myObject.Ensure = 'Present'
            # Mock Test-Path for ValidateInstallerPath()
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\Installer.exe"} {
                return $true
            }
            Mock Start-Process  -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$FilePath -eq "C:\Software\Installer.exe"} {
                return { }
            }
            $results = $myObject.Set()
            $results | should be $null
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
            Assert-MockCalled Start-Process -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }

        It 'Should Execute Uninstall if Ensure = Absent' {
            $myObject.Ensure = 'Absent'
            # Mock Get-ItemProperty for GetInstallInformation() Method
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ DisplayName = 'Wazuh Agent 2.1.1'; UninstallString = 'C:\Software\UnInstall.exe' }
            }
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\UnInstall.exe"} {
                return $true
            }
            #Mock Start-Process for WazuhInstaller() Method
            Mock Start-Process  -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$FilePath -eq "C:\Software\UnInstall.exe"} {
                return { }
            }
            $results = $myObject.Set()
            $results | should be $null
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
            Assert-MockCalled Start-Process -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }
    }

    Context 'Testing the ValidateInstallerPath() Method' {
        #Instantiate Object
        $myObject = [WazuhAgentInstall]::new()
        $myObject.InstallerPath = "C:\Software\Installer.exe"
        It 'Should return TRUE if InstallerPath is valid' {
            $myObject.Ensure = 'Present'
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\Installer.exe"} {
                return $true
            }
            $results = $myObject.ValidateInstallerPath($myObject.InstallerPath)
            $results | should be $true
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }

        It 'Should throw exception for invalid Path' {
            $myObject.Ensure = 'Present'
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\Installer.exe"} {
                return $false
            }
            {$myObject.ValidateInstallerPath("C:\Software\Installer.exe")} | should throw "FileNotFoundException"
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }
    }

    Context 'Testing the VersionUpgrade() Method' {
        #Instantiate Object
        $myObject = [WazuhAgentInstall]::new()
        $myObject.InstallerPath = "C:\Software\Installer.exe"
        It 'Should return FALSE if the Current Version Matches the Provided Installer' {
            $myObject.Ensure = 'Present'
            # Mock Test-Path for ValidateInstallerPath()
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\Installer.exe"} {
                return $true
            }
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'C:\Software\Installer.exe'} {
                return @{
                    VersionInfo = @{
                        FileVersion = "2.1.1";
                        CompanyName = "Wazuh"
                    }
                }
            }
            $result = $myObject.VersionUpgrade("2.1.1")
            $result | Should be $false
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }

        It 'Should return TRUE if the Current Version does NOT MATCH Provided Installer' {
            $myObject.Ensure = 'Present'
            # Mock Test-Path for ValidateInstallerPath()
            Mock Test-Path -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq "C:\Software\Installer.exe"} {
                return $true
            }
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'C:\Software\Installer.exe'} {
                return @{
                    VersionInfo = @{
                        FileVersion = "3.0.0";
                        CompanyName = "Wazuh"
                    }
                }
            }
            $result = $myObject.VersionUpgrade("2.1.1")
            $result | Should be $true
            Assert-MockCalled Test-Path -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }

    }

    Context 'Testing the WazuhInstaller() Method' {
        #Instantiate Object
        $myObject = [WazuhAgentInstall]::new()
        $myObject.InstallerPath = "C:\Software\Installer.exe"
        It 'Should return NULL on successful installation' {
            $myObject.Ensure = 'Present'
            #Mock Start-Process for WazuhInstaller() Method
            Mock Start-Process  -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$FilePath -eq "C:\Software\Installer.exe"} {
                return { }
            }
            $myObject.WazuhInstaller($myObject.InstallerPath) | Should be $null
            Assert-MockCalled Start-Process -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }

        It 'Should THROW exception on error of installation' {
            {$myObject.WazuhInstaller("C:\Software\Nothing.exe")} | Should throw
        }
    }

    Context 'GetInstallerInformation() Method' {
        $myObject = [WazuhAgentInstall]::new()
        It 'Should return NULL if there is no Wazuh Entry' {
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return { }
            }
            $myObject.GetInstallInformation() | Should be $null
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }

        It 'Should return NOT NULL if Wazuh Entry found in Registry' {
            Mock Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -ParameterFilter {$Path -eq 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'} {
                return @{ DisplayName = 'Wazuh Agent 2.1.1'; DisplayVersion = "2.1.1" }
            }
            $myObject.GetInstallInformation() | Should not be $null
            Assert-MockCalled Get-ItemProperty -ModuleName 'WazuhOSSecDSC' -Times 1 -Exactly -Scope it
        }
    }
}