pester / Pester

Pester is the ubiquitous test and mock framework for PowerShell.
https://pester.dev/
Other
3.11k stars 473 forks source link

Pester for Non-Terminating Error #366

Closed dotps1 closed 9 years ago

dotps1 commented 9 years ago

I reviewed the Should Wiki Page, and i apologize if i missed how to use this, but i was working with some error handling, and i want to Pester for non terminating errors as well as Exceptions. Basically, i have some tests in a function that are in the Process block, and i still want the End block to process, so in this case i want to use Write-Error rather then Throw, so, in my pester assertion, i want to make sure the Catch block is being hit, and the Write-Error is being executed, and im not sure how to do that.

It 'Error should be raised' {
     My-FunctionThatWritesErrorForInvalidPath -Path '.\InvalidPath' | Should #Here is where im lost, maybe Should Error would be nice.
}

I hope that makes sense and im sorry if im just using Write-Error incorrectly, and i could use the 'Should Throw' assertion.

thanks for any help.

ChaseFlorell commented 9 years ago
My-FunctionThatWritesErrorForInvalidPath -Path '.\InvalidPath' | Should Throw
dlwyatt commented 9 years ago

Should Throw only works for terminating errors, so you can do one of two things. You can either use -ErrorAction Stop to force your non-terminating errors to become terminating (though this would prevent your End block from running), or you can use something like -ErrorVariable and then do assertions against that. Something along these lines:

It 'Error should be raised' {
     My-FunctionThatWritesErrorForInvalidPath -Path '.\InvalidPath' -ErrorVariable err
     $err.Count | Should Not Be 0
     $err[0].Exception.Message | Should Be "Path '.\InvalidPath' is not valid" # or something like that
}

# or

It 'Error should be raised' {
     $scriptBlock = { My-FunctionThatWritesErrorForInvalidPath -Path '.\InvalidPath' -ErrorAction Stop }
     $scriptBlock | Should Throw "Path '.\InvalidPath' is not valid" # or something like that
}
dotps1 commented 9 years ago

thanks Dave, that works fine for me! keep up the good work! feel free to close this thread if you wish.

ChaseFlorell commented 9 years ago

Ah, sorry, I didn't read the question all the way through. I personally always throw and then run cleanup code in finally. I see you're using a different (and valid) approach.

theficus commented 9 years ago

I work around this with two helper functions that I call in all my tests:

Function InvokeShouldFail
{
    Param([Parameter(Mandatory)][string]$script)
    Write-Host Invoking command and expecting failure: $script
    $didFail = $false
    try
    {
        iex $script
        Write-Warning Did not fail: $script
    }
    catch
    {
        Write-Host "Script failed as expected: $_"
        $didFail = $true
    }

    $didFail | Should Be $true
}
Function InvokeShouldError {
    Param([Parameter(Mandatory)][string]$script)
    Write-Host Invoking command and expecting error: $script
    $didError = ""
    iex $script -ErrorVariable didError

    Write-Host Error result: $didError
    $didError | Should Not BeNullOrEmpty
}

Then in my unit test, I call it something like this

It "Does something" {
    # This should fail, but not with a thrown exception
    InvokeShouldError { Remove-Something -Name NotReallyExist }
}
midacts commented 7 years ago

I ran into this as well. Thanks for the help.

fgimian commented 5 years ago

Should Throw only works for terminating errors, so you can do one of two things. You can either use -ErrorAction Stop to force your non-terminating errors to become terminating (though this would prevent your End block from running), or you can use something like -ErrorVariable and then do assertions against that. Something along these lines:

It 'Error should be raised' {
     My-FunctionThatWritesErrorForInvalidPath -Path '.\InvalidPath' -ErrorVariable err
     $err.Count | Should Not Be 0
     $err[0].Exception.Message | Should Be "Path '.\InvalidPath' is not valid" # or something like that
}

# or

It 'Error should be raised' {
     $scriptBlock = { My-FunctionThatWritesErrorForInvalidPath -Path '.\InvalidPath' -ErrorAction Stop }
     $scriptBlock | Should Throw "Path '.\InvalidPath' is not valid" # or something like that
}

Thanks so much for these tips, very helpful.

Just one little note. The first solution will still display the error on the screen which may be rather distracting while running Pester. You may simply add -ErrorAction SilentlyContinue to avoid this happening.

I like the first approach as it also allows you to validate return values.

e.g.

Describe 'Get-RelativePath' {
    Context 'Filesystem Paths' {
        It 'Should return a relative path when given absolute paths' {
            $relativePath = Get-RelativePath `
                -Path 'C:\wow\files\my app' `
                -BasePath 'HKLM:\SOFTWARE\my app' `
                -ErrorVariable errors `
                -ErrorAction SilentlyContinue

            $errors.Count | Should Be 1
            $errors[0].Exception.Message | Should Be (
                'The scheme of the path and base path provided do not match.'
            )

            $relativePath | Should Be $null
        }
    }
...

Cheers Fotis

deadlydog commented 4 years ago

I believe this is probably a pretty common scenario, so I updated the Should documentation to include a blurb about it in the Throw example, with a link back to this issue 🙂

RobJohnson459 commented 3 years ago

I know I'm new and this may be obvious to those who have used this a lot before, but doesn't the stop keyword in the code blocks above need to be in quotes?

In any case, I ran into a situation where I wanted to test that a warning was printing, so I had to make use of the piping operator to test:

Describe 'Get-RelativePath' {
    Context 'Filesystem Paths' {
        It 'Should return a warning when given incorrect paths' {
            $temporaryFile - $PSScriptRoot + '\notARealFile.txt'
            New-Item -Path $temporaryFile -ItemType 'file' -Force
            Get-RelativePath -Path 'C:\invalid\path'  3> $temporaryFile
            $temporaryFile | Should -not -BeNullOrEmpty
            $temporaryFile | Should Be ( 'your Error Message here' )
        }
    }

All this does is pipe all the errors to a file named 'notARealFile.txt' that you can then check yourself.

fflaten commented 3 years ago

doesn't the stop keyword in the code blocks above need to be in quotes?

No. -ErrorAction is of type enum (a pre-defined list of values) and not a string. Even if it was a string parameter, values without whitespace are usually okay (fixed automatically by powershell).

All this does is pipe all the errors to a file named 'notARealFile.txt' that you can then check yourself.

You are actually saving warnings (stream 3), not errors. So this is unrelated to the issue. Tip: If this command is a "advanced function" (ex specifies [CmdLetBinding()]), then you can use Get-RelativePath -Path 'C:\invalid\path' -WarningVariable mywarnings to catch the warnings. If not and if the command shouldn't return any other output in this test that you need, then you can redirect warnings to standard output and store in a variable like normal results, ex $myWarnings = Get-RelativePath -Path 'C:\invalid\path' 3>&1

RobJohnson459 commented 3 years ago

Thanks for the -ErrorAction clarification. And piping to a variable would be a better idea for my use case, thanks.