Code coverage with Pester trouble

This topic contains 4 replies, has 2 voices, and was last updated by Profile photo of Jonathan Warnken Jonathan Warnken 4 months ago.

Viewing 5 posts - 1 through 5 (of 5 total)
  • Author
    Posts
  • #40373
    Profile photo of Michael Willis
    Michael Willis
    Participant

    I recently started trying to add full code coverage to some existing projects. In a few situations I'm not sure how to write tests or refactor to cover a few scenarios.

    In one example from the DSC Repo, a resources uses a while loop to write-verbose errors in a catch block. Below is a rough example that is missing the same coverage areas.

    I've tried using mocks to throw custom exceptions but so far have had little success. Any ideas?

    File         Function  Line Command                                  
    ----         --------  ---- -------                                  
    FindPath.ps1 Find-Path   28 $exception = $exception.innerException   
    FindPath.ps1 Find-Path   29 Write-Verbose -Message $exception.message
    
    function Find-Path
    {
        [CmdletBinding()]
        Param
        (
            [String] $FilePath
        )
    
        try
        {
            if(Test-Path -Path $FilePath)
            {
                Write-Verbose 'File Exists'
                return 1
            }
            else
            {
                Write-Verbose 'File Doesnt Exist'
                return 2
            }
        }
        catch
        {
            $exception = $_
            Write-Verbose -Message $exception.message
            while ($null -ne $exception.innerException)
            {
                $exception = $exception.innerException
                Write-Verbose -Message $exception.message
            }
    
            return 3
        }
    }
    
    $here = Split-Path -Parent $MyInvocation.MyCommand.Path
    $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
    . "$here\$sut"
    
    Describe 'Find-Path' {
        Context 'should do something' {
    
            Mock Write-Verbose
    
            It 'should handle errors' {
                {Find-Path ''} | Should Not Throw
                Find-Path '' | Should Be 3
                Assert-MockCalled -commandName Write-Verbose
            }
            It 'should not exist' {
                {Find-Path '!'} | Should Not Throw
                Find-Path '!' | Should BeExactly 2
                Assert-MockCalled -commandName Write-Verbose
            }
            It 'should exist' {
                {Find-Path 'c:\Windows\regedit.exe'} | Should Not Throw
                Find-Path 'c:\Windows\regedit.exe' | Should Be 1
                Assert-MockCalled -commandName Write-Verbose
            }
    
        }
    }
    
    #40501
    Profile photo of Jonathan Warnken
    Jonathan Warnken
    Participant

    The code coverage report is telling you the tests are not executing the while logic in your catch block.
    you need to examine the code and determine if it is needed and if so come up with a test to exercise it. I can not make your example use that code but I was able to use the following to exercise a similar code block

    Function start-test {
    [CmdletBinding()]
        Param(
            [String] $FilePath
        )
        while ($ex -eq $null){
            $ex = "TEST!!" 
            Write-Verbose -Message $ex
        }
    }
    

    Hope this helps

    #40601
    Profile photo of Michael Willis
    Michael Willis
    Participant

    Thanks for the input, but I don't think this is what I'm looking for. I could refactor the code a bit if it could simplify the tests. The main issue is getting coverage in the catch block.

    The example in this post is something I just made for testing and to illustrate the problem.
    The actual code I'm using looks more like the DSC hosts file.

    #40779
    Profile photo of Jonathan Warnken
    Jonathan Warnken
    Participant

    Take a look at MSDN for for inner exception example as this looks like the catch is similar.

    From the DSC hosts file Test-TargetResource has a try to catch exceptions from Test-HostEntry; which in turn calls Get-Content and Convert-EntryLine; which does some matches and returns an object. Nowhere down the tree is there a case for an exception that is thrown and caught outside of the Test-TargetResource function and then another exception thrown and caught by the Test-TargetResource function.

    So the code coverage report is correctly telling you that there is unused code in the test cases. Based on the code being exercised by the tests it is either unneeded or you have more code to add (in the other functions) to be able to support the test.

    #40831
    Profile photo of Jonathan Warnken
    Jonathan Warnken
    Participant

    It appears that powershell does not keep the source for each throw so each throw in essence overwrites the exception record doping the inter exception info. you can cause this code to work with a mock that executes inline C# like the msdn example

    function test-main{
        [CmdletBinding()]
        Param([String] $test)
        try{
            test-catchinner
        }catch{
            $exception = $_.exception
            Write-Verbose -Message $exception.Message
            #$exception.innerException|gm
            while ($null -ne $exception.innerException)
            {
                $exception = $exception.innerException
                Write-Verbose -Message $exception.Message
            }
        }
        try{
            [Example]::Main()
        }catch{
            $exception = $_.exception
            Write-Verbose -Message $exception.Message
            #$exception.innerException|gm
            while ($null -ne $exception.innerException)
            {
                $exception = $exception.innerException
                Write-Verbose -Message $exception.Message
            }
        }
    }
    Function test-catchinner{
        try{
            test-throwinner
        }catch{
            throw "Error in test-catchinner caused by test-throwinner"
        }
    }
    Function test-throwinner{
        throw "Error in test-throwinner"
    }
    $source = @'
    using System;
    public class AppException : Exception
    {
       public AppException(String message) : base (message)
       {}
    
       public AppException(String message, Exception inner) : base(message,inner) {}
    }
    
    public class Example
    {
       public static void Main()
       {
          Example ex = new Example();
    
          ex.CatchInner();
    
       }
    
       public void ThrowInner ()
       {
          throw new AppException("Exception in ThrowInner method.");
       }
    
       public void CatchInner() 
       {
          try {
             this.ThrowInner();
          }
          catch (AppException e) {
             throw new AppException("Error in CatchInner caused by calling the ThrowInner method.", e);
          }
       }
    }
    '@
    Add-Type -TypeDefinition $source -Language CSharp 
    
    test-main -Verbose
    
    VERBOSE: Error in test-catchinner caused by test-throwinner
    VERBOSE: Exception calling "Main" with "0" argument(s): "Error in CatchInner caused by calling the ThrowInner method."
    VERBOSE: Error in CatchInner caused by calling the ThrowInner method.
    VERBOSE: Exception in ThrowInner method.
    

    The powershell functions only show that last throw while the C# code shows that whole chain via the innerexecption

    Hope this helps

Viewing 5 posts - 1 through 5 (of 5 total)

You must be logged in to reply to this topic.