Author Posts

July 28, 2018 at 2:30 am

My psm1 file dot sources all functions in my development setup, I export all function module members. I'm trying to learn Pester and I'm stuck on figuring out how to have the test.ps1 file load the function.ps1 file which relies on importing the module. Here's a sample of my folder structure if this helps.

This function will eventually compare the repo latest version with the current version and alert the user that there is an update. Initially, I need to get the module version from the function. The problem is I'm not sure how to get the pester test to work correctly.

C:\..\Docs
C:\..\en-us
C:\..\Private\Get-RepoModuleVersion.ps1
C:\..\Public
C:\..\Tests\Get-RepoModuleVersion.test.ps1
C:\..\expFTP.psd1
C:\..\expFTP.psm1

Get-RepoModuleVersion.ps1

Function Get-RepoModuleVersion {
    [CmdletBinding()]
    Param(
        [Parameter()]
        [Switch]$Latest
    )

    Begin {}
    
    Process {
        (Get-command $MyInvocation.MyCommand).version
    }

    End {}
}

Get-RepoModuleVerson.test.ps1

$projectRoot = Resolve-Path "$PSScriptRoot\.."
."$projectRoot\Private\Get-RepoModuleVersion.ps1"

Import-module "$projectRoot\expftp.psd1" -force
InModuleScope expFTP {
    Describe 'Get-RepoModuleVersion' {
        Mock 'Get-command' {'4.6.3.7'}
        Context 'Input' {
            {Get-RepoModuleVersion -Latest } | Should be '4.6.3.7'
        }
        Context 'Execution' {}
        Context 'Output' {}
    }
}

July 30, 2018 at 1:16 pm

I'm perhaps not following – wouldn't you just import the module?

July 30, 2018 at 2:54 pm

Thank's Don. I noticed I left out the It block by mistake and didn't catch it. The other issue Is I don't think I was mocking get-command correctly as I'm mocking it with a string then calling a property of that object. I did rewrite my function and test another way and it passes now. I also renamed the function.

If my logic is not too difficult to follow, do you think my test is adequate or should I add additional tests? In a nutshell, the function will be called from the Begin Blocks of my public functions to notify the end user that there is an update to the module by comparing the local and remote repo versions.

Compare-expFtpRepoModuleVersion.ps1

Function Compare-expFtpRepoModuleVersion {
    [CmdletBinding()]
    Param()

    Begin {}
    
    Process {
        $LocalModuleVersion = (Get-command $MyInvocation.MyCommand)
        Try {
            $RemoteModuleVersion = Find-Module -Name $LocalModuleVersion.Source -ErrorAction Stop
            if ($RemoteModuleVersion){
                if ($LocalModuleVersion.Version -LT $RemoteModuleVersion.Version) {
                    $Update = 'Yes'
                }
                Else {
                    $Update = 'No'
                }
                $Properties = @{
                    LocalVersion  = $LocalModuleVersion.Version
                    RemoteVersion = $RemoteModuleVersion.Version
                    CheckTime     = (get-date)
                    Update        = $Update
                    Module        = $LocalModuleVersion.Source
        
                }
                $obj = New-Object -TypeName PSObject -Property $properties
                Set-Variable -Name "$($LocalModuleVersion.Source)Module" -Value $obj -Scope Global

            }
        }
        Catch {}
    }

    End {}
}

Compare-expFtpRepoModuleVersion.test.ps1

$projectRoot = Resolve-Path "$PSScriptRoot\.."
."$projectRoot\Private\Compare-expFtpRepoModuleVersion.ps1"

Import-module "$projectRoot\expftp.psd1" -force
InModuleScope expFTP {
    Describe 'Get-RepoModuleVersion' {
        Mock 'Get-command'
        Context 'Input' {
            it 'should return the correct type' {
                {Compare-expFtpRepoModuleVersion} | Should beoftype PSCustomObject
            }
        }
        Context 'Execution' {}
        Context 'Output' {}
    }
}

July 30, 2018 at 3:00 pm

So, a trick with testing is to make sure you're also testing the "negative." Meaning, don't just check to make sure it does the correct thing, make sure it also deals well with an "incorrect" situation.

Additionally, I wouldn't return a PSCustomObject. Give that thing a custom type name and check for that – PSCustomObject is way too common.

Right now, you're just checking for a type name – do you need to care about the properties, or their values, for a given input? What about situations where "something went wrong," like something you expected to exist isn't installed? Will it deal with those situations correctly?

July 30, 2018 at 3:12 pm

could you elaborate on a custom type name? I don't think I'm familiar with that. I would also need to test the object to make sure the properties are returned as expected. I guess for that I would have to mock Get-Command and Find-Module with some fake properties that resemble the actual format of a real Get-Command and Find-Module call. As for testing a failure, I think mocking Get-Command and/or Find-Module with a Throw should do the trick, right?

I'll have to do a bit of research and go further into the Pester Book

July 30, 2018 at 3:16 pm

$obj.psobject.typenames.insert(0,"My.Custom.Type.Name")

Or, peruse "Learn PowerShell Scripting in a Month of Lunches," which covers it pretty extensively ;).

I guess for that I would have to mock Get-Command and Find-Module with some fake properties that resemble the actual format of a real Get-Command and Find-Module call.

Well, no. Like, what's the goal? Are you testing to see if Get-Command works? Don't bother; if it's broken, you can't fix it. You should be testing YOUR commands, not built-in ones. Presume that Get-Command works correctly, because if it doesn't, we're all screwed big-time. Same with Find-Module; you don't test other people's commands. So if the logic here is, "I want to use Get-Command to make sure my command is installed," then you don't write a test for that. Get-Command will work as designed.

July 30, 2018 at 3:26 pm

Got it! I didn't read Scripting in a month of lunches, but I do have the PowerShell scripting and Toolmaking book, so I think I will have to go back and review. Thanks for the help!