Open johlju opened 1 week ago
-Not -Throw
is left out intentionally atm. We kinda discussed this on discord in May when you requested more verbose error output. 🙂
From #testing:
How about running the code without
Should -Not -Throw
? The exception itself will fail the test and show full stack. Not throw mostly provides a simple error message which you found too simple.
Did you find a scenario where Should -Not -Throw
still makes sense?
We kinda discussed this on discord in May
Right... remember that. 🙂 Didn't thought it would apply here. 😉
Did you find a scenario where Should -Not -Throw still makes sense?
Only when automating conversion of test scripts. E.g. only in SqlServerDsc there are 788 lines using the syntax
{ ... } | Should -Not -Throw
. Changing them to not pass a scriptblock through the pipeline and not use -Not
manually would take a long time. I have now code ready that can easily convert Pester 5 syntax to either Should-NotThrow
or Should-Throw
with -Not
. If we are not considering adding either the command Should-NotThrow
or have -Not
added to Should-Throw
then I need to look into code that can convert all { ... } | Should -Not -Throw
tests so they just run the code inside the scriptblock and without passing it to Should-*
. 🤔
AFAIK it's not set in stone, but by starting without it we get the discussion. 🙂
If it can be covered by a simple migration step trough docs, made even easier by your automated module, that'd be cool.
The easiest conversion is probably to replace Should -Not -Throw
with ForEach-Object { & $_ } | Out-Null
as it would also support the undocumented support for multiple scriptblocks through pipeline. E.g.
# Defining outside to be available for test in the end
$scriptBlock = { throw 'one' }
# Intentionally failing to confirm they actually executed without syntax errors
$source = {
$scriptBlock | Should -Not -Throw
{ throw 'two' } | Should -Not -Throw -Because 'why not'
{ throw 'three' } |
Should -Not -Throw -Because 'why not' -ExpectedMessage 'ignore'
Should -Not -Throw -Because 'why not' -ExpectedMessage 'ignore' -ActualValue { throw 'four' }
# I'm sure there's one out there :D
"throw 'five'" | ForEach-Object { [scriptblock]::Create($_) } | should -Throw -Not
# Fails on second block. Hope nobody use this
{ 'works' }, { throw 'six' } | Should -Throw -Not
}
$Ast = $source.Ast
$todo = $Ast.FindAll({ param($a) $a -is [System.Management.Automation.Language.CommandAst] -and $a.GetCommandName() -eq 'Should' }, $true)
$todo | ForEach-Object {
$pipelineElements = $_.Parent.PipelineElements
$actualValue = $null
if ($pipelineElements.Count -eq 1) {
# Should -Not -Throw -ActualValue { something }
# AFAIK positional doesn't work so being naive. 99% probably use pipeline input like docs
for ($i = 0; $i -lt $_.CommandElements.Count; $i++) {
$p = $_.CommandElements[$i]
if ($p -is [System.Management.Automation.Language.CommandParameterAst] -and $p.ParameterName -like 'A*') {
$actualValue =$_.CommandElements[$i + 1].Extent.Text
break
}
}
} elseif ($pipelineElements.Count -gt 1 -and $pipelineElements[-1] -eq $_) {
# something | maybe something else | Should -Not -Throw
$replaceStart = $_.Extent.StartOffset - $_.Parent.Extent.StartOffset
$actualValue = $_.Parent.Extent.Text.Remove($replaceStart) -replace '\s*\|\s*$'
} else {
throw 'What sorcery is this?'
}
# New parent extent
$newParentExtentText = "$($actualValue) | ForEach-Object { & `$_ } | Out-Null # TODO: Cleanup. Automatic conversion from Should -Not -Throw"
# Test
[System.Management.Automation.Language.Parser]::ParseInput($newParentExtentText, [ref]$null, [ref]$null).GetScriptBlock().Invoke()
}
Thanks for the code example, and the suggestions for use of ForEach-Object
, did not think of that. It is the safest (only) option if scriptblocks are passed as variables to Should
. Maybe I will try to have both options 1) using ForEach-Object
when there are a variable and 2) if a scriptblock, parse out the code in the scriptblock so it can run directly in the It-block as it is currently meant to do in Pester 6 alpha. 🤔 I will see how it works out. 🙂
Btw. using ForEach-Object
outputs a bit more line numbers (first one) then having the code directly in the scriptblock (second one). So to avoid using ForEach-Object
would be cleaner I think. 🤔
It 'Should not throw, passing to ForEach-Object' {
{
Write-Error -Message 'MockErrorMessage' -ErrorId 'MockErrorId' -Category 'InvalidOperation' -TargetObject 'MockTargetObject' -ErrorAction 'Stop'
} | ForEach-Object -Process { & $_ } | Out-Null
}
It 'Should not throw, without any scriptblock' {
Write-Error -Message 'MockErrorMessage' -ErrorId 'MockErrorId' -Category 'InvalidOperation' -TargetObject 'MockTargetObject' -ErrorAction 'Stop'
}
Outputs:
[-] Should not throw, passing to ForEach-Object 19ms (14ms|5ms)
WriteErrorException: MockErrorMessage
at <ScriptBlock>, /Users/johlju/source/PesterConverter/tests/Unit/Pester6.tests.ps1:42
at <ScriptBlock>, /Users/johlju/source/PesterConverter/tests/Unit/Pester6.tests.ps1:43
at <ScriptBlock>, /Users/johlju/source/PesterConverter/tests/Unit/Pester6.tests.ps1:39
[-] Should not throw, without any scriptblock 10ms (6ms|4ms)
WriteErrorException: MockErrorMessage
at <ScriptBlock>, /Users/johlju/source/PesterConverter/tests/Unit/Pester6.tests.ps1:57
You can invoke a scriptblock-variable with call operator &/.
. E.g. $null = & (<actualvalue>)
should work for variables, script blocks and expressions (note parentheses)
Only reason I didn't use it was the edge case of multiple script blocks, which you can probably ignore.
Running the code directly will send data to StandardOutput in the Test-object. Not a big deal unless there's a lot of output, but might as well assign it to null like -Throw
.
Then I go with $null = & (<actualvalue>)
, that will simplify and generalize it and avoid running code directly in the It-block to not mess up the Test-object (there could probably be a lot of output). 🙂
Checklist
What is the issue?
In Pester 5 syntax we could say
Should -Not -Throw
. I can't find a way to negate theShould-Throw
command as it does not have aNot
parameter or a commandShould-NotThrow
.Expected Behavior
Possibility to evaluate a command to not throw.
Steps To Reproduce
not applicable
Describe your environment
Possible Solution?
Either add a
Not
parameter to the commandShould-Throw
or add a new commandShould-NotThrow
?