Alan T
Alan T

Reputation: 3344

Mocking a command and getting different results based on the number of times the Mock is called

I'm using Pester to unit test some code I've written. In the tests I mock Test-Path using a parameter filter:

Mock -CommandName 'Test-Path' -MockWith { return $false } `
    -ParameterFilter { $LiteralPath -and $LiteralPath -eq 'c:\dummy.txt' }

The following is psuedo code for what I'm doing:

If ( Test-Path -LiteralPath c:\dummy.txt )
{
    return "Exists"
}
else
{
    Attempt to get file

    If ( Test-Path -LiteralPath c:\dummy.txt )
    {
        return "File obtained"
    }   
}

On the first call to Test-Path I want to return $false and on the second call I want to return $true. I can think of a couple of ways of achieving this, but they don't feel right:

  1. On the first call use the Path parameter and on the second use LiteralPath. Have two mocks one with a ParameterFilter for each. I don't like the idea of hacking the code in order to facilitate a test.

  2. Create a function with parameters for: Path and InstanceNumber. Create mocks for the function. This is better than the above, but I don't like the idea of having a parameter just for testing purposes.

I've looked and can't find a way to mock based on the nth call. Does Pester facilitate this and I've just missed it? If not is there a cleaner way of achieving what I want?

Upvotes: 2

Views: 2490

Answers (2)

NotoriousPyro
NotoriousPyro

Reputation: 647

The problem probably lies in how you are writing your functions is making the testing unwieldy as the functions are probably becoming the same...

Instead you should abstract functionality out of your main function which allows you to test them individually. I don't know your code but this is just my 2 cents...

function MyFunction {
    param (
        $Path
    )

    $exists = (TestPathFirstCall $Path) -eq $true

    if (-not $exists) {
        $exists = (TryToCreateTheFile $Path) -eq $true
    }

    return $exists
}

function TestPathFirstCall {
    param (
        [string] $Path
    )
    Test-Path $Path
}

function TryToCreateTheFile {
    param (
        [string] $Path
    )
    New-Item $Path
    Test-Path $Path
}


Describe 'Test-Path Tests' {
    It 'Tries Test-Path twice, fails first time and returns true' {
        Mock TestPathFirstCall {
            return $false
        }

        Mock TryToCreateTheFile {
            return $true
        }

        MyFunction "C:\dummy.txt" | Should BeExactly $true
        Assert-MockCalled -Exactly TestPathFirstCall -Scope It -Times 1
        Assert-MockCalled -Exactly TryToCreateTheFile -Scope It -Times 1
    }

    It 'Tries Test-Path once and succeeds' {
        Mock TestPathFirstCall {
            return $true
        }

        Mock TryToCreateTheFile {
            return $true
        }

        MyFunction "C:\dummy.txt" | Should BeExactly $true
        Assert-MockCalled -Exactly TestPathFirstCall -Scope It -Times 1
        Assert-MockCalled -Exactly TryToCreateTheFile -Scope It -Times 0
    }

    It 'Tries Test-Path twice and fails' {
        Mock TestPathFirstCall {
            return $false
        }

        Mock TryToCreateTheFile {
            return $false
        }

        MyFunction "C:\dummy.txt" | Should BeExactly $false
        Assert-MockCalled -Exactly TestPathFirstCall -Scope It -Times 1
        Assert-MockCalled -Exactly TryToCreateTheFile -Scope It -Times 1
    }
}

Upvotes: 0

scorpio
scorpio

Reputation: 1625

function Test-File{
    If ( Test-Path -LiteralPath c:\dummy.txt )
            {
                return "Exists"
            }
            else
            {
                If ( Test-Path -LiteralPath c:\dummy.txt )
                {
                    return "File obtained"
                }   
            }
}


Describe "testing files" {
    it "file existence test" {
        #Arrange
        $script:mockCalled = 0
        $mockTestPath = {
                            $script:mockCalled++                                
                            if($script:mockCalled -eq 1)
                            {
                                return $false
                            }
                            else
                            {
                                return $true
                            }
                        }

            Mock -CommandName Test-Path -MockWith $mockTestPath 
            #Act
            $callResult = Test-File 

            #Assert
            $script:mockCalled | Should Be 2
            $callResult | Should Be "File obtained"
            Assert-MockCalled Test-Path -Times $script:mockCalled -ParameterFilter { $LiteralPath -and $LiteralPath -eq 'c:\dummy.txt' }


        }
}

I think you are after this?! let me know if not!

Upvotes: 5

Related Questions