Skip to content

Commit

Permalink
Add support for Assert-MockCalled (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
johlju authored Feb 16, 2025
1 parent 24948be commit 74731a7
Show file tree
Hide file tree
Showing 20 changed files with 1,278 additions and 201 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Public commands:
- `Convert-PesterSyntax`
- Add support for Should operators:
- Add support for `Should` operators:
- Be
- BeExactly
- BeFalse
Expand All @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Match
- MatchExactly
- Throw
- Add support for `Assert-MockCalled`.
- Added new parameter `OutputPath` to write the resulting file to
a separate path.
- Add integration tests.
Expand Down Expand Up @@ -58,3 +59,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Also supports when Switch parameters is the last parameter on the extent.
- Updated conversion documentation for `Should -Invoke`, `Should -Not -Invoke`
and `Should -HaveCount`.
- Now converting `Should -HaveCount` works when `-Not:$false` is specified.
250 changes: 250 additions & 0 deletions source/Private/Convert-AssertMockCalled.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
<#
.SYNOPSIS
Converts a command `Assert-MockCalled` to `Should -Invoke` or `Should-Invoke`.
.DESCRIPTION
The Convert-AssertMockCalled function is used to convert a command `Assert-MockCalled`
to `Should -Invoke` or `Should-Invoke`.
.PARAMETER CommandAst
The CommandAst object representing the command to be converted.
.PARAMETER Pester5
Specifies that the command should be converted to Pester version 5 syntax.
.PARAMETER Pester6
Specifies that the command should be converted to Pester version 6 syntax.
.PARAMETER UseNamedParameters
Specifies whether to use named parameters in the converted syntax.
.PARAMETER UsePositionalParameters
Specifies whether to use positional parameters in the converted syntax,
where supported.
.EXAMPLE
$commandAst = [System.Management.Automation.Language.Parser]::ParseInput('Assert-MockCalled -CommandName "TestCommand"')
Convert-AssertMockCalled -CommandAst $commandAst -Pester5
This example converts the `Assert-MockCalled -CommandName "TestCommand"` command to `Should -Invoke`.
.EXAMPLE
$commandAst = [System.Management.Automation.Language.Parser]::ParseInput('Assert-MockCalled -CommandName "TestCommand"')
Convert-AssertMockCalled -CommandAst $commandAst -Pester6
This example converts the `Assert-MockCalled -CommandName "TestCommand"` command to `Should-Invoke`.
.NOTES
Pester 4/5 Syntax:
Assert-MockCalled [-CommandName] <String> [[-Times] <Int32>] [[-ParameterFilter] <ScriptBlock>] [[-ModuleName] <String>] [[-Scope] <String>] [-Exactly] [<CommonParameters>]
Assert-MockCalled [-CommandName] <String> [[-Times] <Int32>] -ExclusiveFilter <ScriptBlock> [[-ModuleName] <String>] [[-Scope] <String>] [-Exactly] [<CommonParameters>]
Positional parameters:
Position 1: CommandName
Position 2: Times
Position 3: ParameterFilter
Position 4: ModuleName
Position 5: Scope
Pester 5/6 Syntax:
Should -Invoke [-CommandName] <string> [[-Times] <int>] [-ParameterFilter <scriptblock>] [-ModuleName <string>] [-Scope <string>] [-Exactly] [-ActualValue <Object>] [-Not] [-Because <string>] [<CommonParameters>]
Should -Invoke [-CommandName] <string> [[-Times] <int>] -ExclusiveFilter <scriptblock> [-ParameterFilter <scriptblock>] [-ModuleName <string>] [-Scope <string>] [-Exactly] [-ActualValue <Object>] [-Not] [-Because <string>] [<CommonParameters>]
Positional parameters:
Position 1: CommandName
Position 2: Times
#>
function Convert-AssertMockCalled
{
[CmdletBinding()]
[OutputType([System.String])]
param
(
[Parameter(Mandatory = $true)]
[System.Management.Automation.Language.CommandAst]
$CommandAst,

[Parameter(Mandatory = $true, ParameterSetName = 'Pester6')]
[System.Management.Automation.SwitchParameter]
$Pester6,

[Parameter(Mandatory = $true, ParameterSetName = 'Pester5')]
[System.Management.Automation.SwitchParameter]
$Pester5,

[Parameter()]
[System.Management.Automation.SwitchParameter]
$UseNamedParameters,

[Parameter()]
[System.Management.Automation.SwitchParameter]
$UsePositionalParameters
)

$assertBoundParameterParameters = @{
BoundParameterList = $PSBoundParameters
MutuallyExclusiveList1 = @('UseNamedParameters')
MutuallyExclusiveList2 = @('UsePositionalParameters')
}

Assert-BoundParameter @assertBoundParameterParameters

Write-Debug -Message ($script:localizedData.Convert_AssertMockCalled_Debug_ParsingCommandAst -f $CommandAst.Extent.Text)

$sourceSyntaxVersion = Get-PesterCommandSyntaxVersion -CommandAst $CommandAst

# Parse the command elements and convert them to Pester 6 syntax
if ($PSCmdlet.ParameterSetName -in @('Pester5', 'Pester6'))
{
<#
Always convert to Pester 5 syntax. Converting to Pester 5 is an intermediate
step for Pester 6 syntax, which will be used to convert to Pester 6 at the end.
#>
$debugMessage = $script:localizedData.Convert_AssertMockCalled_Debug_ConvertingFromTo -f $sourceSyntaxVersion, 5

if ($PSCmdlet.ParameterSetName -eq 'Pester6')
{
$debugMessage += ' {0}' -f $script:localizedData.Convert_AssertMockCalled_Debug_IntermediateStep
}

Write-Debug -Message $debugMessage

# Add the correct Pester 5 command.
$newExtentText = 'Should -Invoke'

$getPesterCommandParameterParameters = @{
CommandAst = $CommandAst
CommandName = 'Assert-MockCalled'
IgnoreParameter = @()
PositionalParameter = @(
'CommandName',
'Times',
'ParameterFilter',
'ModuleName',
'Scope'
)
NamedParameter = @(
'Exactly'
)
}

$commandParameters = Get-PesterCommandParameter @getPesterCommandParameterParameters

<#
Parameter 'ParameterFilter', 'ModuleName' and 'Scope' are only supported
as named parameters in Pester 5 and Pester 6 syntax.
#>
@(
'ParameterFilter'
'ModuleName'
'Scope'
) | ForEach-Object -Process {
if ($commandParameters.$_)
{
$commandParameters.$_.Positional = $false
}
}

# Determine if named or positional parameters should be forcibly used
if ($UseNamedParameters.IsPresent)
{
$commandParameters.Keys.ForEach({ $commandParameters.$_.Positional = $false })
}
elseif ($UsePositionalParameters.IsPresent)
{
# First set all to named parameters
$commandParameters.Keys.ForEach({ $commandParameters.$_.Positional = $false })

<#
If a previous positional parameter is missing then the ones behind
it cannot be set to positional.
#>
if ($commandParameters.CommandName)
{
$commandParameters.CommandName.Positional = $true

if ($commandParameters.Times)
{
$commandParameters.Times.Positional = $true
}
}
}

# Add positional parameters in the correct order.
$newExtentText += $commandParameters.CommandName.Positional ? (' {0}' -f $commandParameters.CommandName.ExtentText) : ''
$newExtentText += $commandParameters.Times.Positional ? (' {0}' -f $commandParameters.Times.ExtentText) : ''

# Prepare remaining parameters as named parameters in alphabetical order.
$parameterNames = @{}

foreach ($currentParameter in $commandParameters.Keys)
{
if ($commandParameters.$currentParameter.Positional -eq $true)
{
continue
}

switch ($currentParameter)
{
# There are no parameters that need to be converted to different names.

default
{
$parameterNames.$currentParameter = $currentParameter

break
}
}
}

# This handles the named parameters in the command elements, added in alphabetical order.
foreach ($currentParameter in $parameterNames.Keys | Sort-Object)
{
$originalParameterName = $parameterNames.$currentParameter

$newExtentText += ' -{0}' -f $currentParameter

if ($commandParameters.$originalParameterName.ExtentText)
{
$newExtentText += ' {0}' -f $commandParameters.$originalParameterName.ExtentText
}
}

# Convert the extent to Pester 6 if necessary
if ($PSCmdlet.ParameterSetName -eq 'Pester6')
{
Write-Debug -Message ($script:localizedData.Convert_AssertMockCalled_Debug_ConvertingFromTo -f 5, 6)

$scriptBlockAst = [System.Management.Automation.Language.Parser]::ParseInput($newExtentText, [ref] $null, [ref] $null)

$commandAsts = $scriptBlockAst.FindAll({
$args[0] -is [System.Management.Automation.Language.CommandAst]
}, $true)

$newExtentAst = $commandAsts | Select-Object -First 1

$convertShouldInvokeParameters = @{
Pester6 = $true
CommandAst = $newExtentAst
}

if ($UseNamedParameters.IsPresent)
{
$convertShouldInvokeParameters.UseNamedParameters = $true
}

if ($UsePositionalParameters.IsPresent)
{
$convertShouldInvokeParameters.UsePositionalParameters = $true
}


$newExtentText = Convert-ShouldInvoke @convertShouldInvokeParameters
}
}

Write-Debug -Message ($script:localizedData.Convert_AssertMockCalled_Debug_ConvertedCommand -f $CommandAst.Extent.Text, $newExtentText)

return $newExtentText
}
1 change: 1 addition & 0 deletions source/Private/Convert-ShouldHaveCount.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ function Convert-ShouldHaveCount
CommandName = 'Should'
IgnoreParameter = @(
'HaveCount'
'Not'
)
PositionalParameter = @(
'ExpectedValue'
Expand Down
16 changes: 15 additions & 1 deletion source/Private/ConvertTo-ActualParameterName.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function ConvertTo-ActualParameterName
param
(
[Parameter(Mandatory = $true)]
[ValidateSet('Should')]
[ValidateSet('Assert-MockCalled', 'Should')]
[System.String]
$CommandName,

Expand All @@ -41,6 +41,20 @@ function ConvertTo-ActualParameterName

switch ($CommandName)
{
# Assert-MockCalled in Pester 4.
'Assert-MockCalled'
{
$parameterNames = @(
'CommandName'
'Times'
'ParameterFilter'
'ModuleName'
'Scope'
'Exactly'
'ExclusiveFilter'
)
}

# Should in Pester 5.
'Should'
{
Expand Down
55 changes: 55 additions & 0 deletions source/Private/Get-CommandName.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<#
.SYNOPSIS
Retrieves the command name from the provided CommandAst.
.DESCRIPTION
The Get-CommandNameFromAst function analyzes the CommandAst object to extract
and return the command name. It handles different AST element types to ensure
the command name is accurately determined.
.PARAMETER CommandAst
Specifies the CommandAst object representing the command from which the name is extracted.
.OUTPUTS
System.String
The command name as a string.
.EXAMPLE
$commandAst = [System.Management.Automation.Language.Parser]::ParseInput('Get-Process -Name "powershell"')
Get-CommandName -CommandAst $commandAst
Returns "Get-Process"
#>

function Get-CommandName
{
[CmdletBinding()]
[OutputType([System.String])]
param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Management.Automation.Language.CommandAst]
$CommandAst
)

process
{
# Retrieve the first command element which represents the command name.
$commandElement = $CommandAst.CommandElements[0]

# Use a switch to handle common AST types for the command name.
switch ($commandElement.GetType().Name)
{
'StringConstantExpressionAst'
{
return $commandElement.Value
}

default
{
return $null
}
}
}
}
6 changes: 5 additions & 1 deletion source/Private/Get-PesterCommandSyntaxVersion.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ function Get-PesterCommandSyntaxVersion

$sourceSyntaxVersion = $null

if ($CommandAst.CommandElements[0].Extent.Text -match 'Should-\w+\b')
if ($CommandAst.CommandElements[0].Extent.Text -match 'Assert-MockCalled')
{
$sourceSyntaxVersion = 4
}
elseif ($CommandAst.CommandElements[0].Extent.Text -match 'Should-\w+\b')
{
$sourceSyntaxVersion = 6
}
Expand Down
Loading

0 comments on commit 74731a7

Please sign in to comment.