Mock when resource is not available?

194 views
Skip to first unread message

craig buchanan

unread,
Jun 1, 2016, 9:45:47 AM6/1/16
to Pester
I have a number of functions that return the results of a database query.  There are times when the database isn't available (when I'm on a plane, for example).  In these case, I'd like to mock a result set:

Describe 'Get-FooResults' {

  Context 'When the default parameter values are supplied' {

    if (Test-DbConnection) {
      Mock Invoke-SqlCmd {
        return @{ Field0=1; Field1='ABC'; Field2='03/01/2016' }
      }
    } # /if

    It 'Produces a hash with desired keys' {
      $expected = @{ Field0=0; Field1=''; Field2='' }
      $actual = Get-FooResults
      @(Compare-Object ($actual.keys) ($expected.keys)).Length | should be 0
    }

    # additional tests...

  } # /Context

  # additional contexts...

} # /Describe

Is this a good pattern?

An alternative, or perhaps an additional test, would be to compare the SQL to an expected value.  Is there a way to capture and test the output of Write-Debug?  Is this a good idea?

Thanks.

Matt Wrock

unread,
Jun 1, 2016, 9:59:54 AM6/1/16
to craig buchanan, Pester

I personally prefer to have separate test suites that test at varying levels of the stack. So I might put all my “unit” tests under a “tests/unit” folder and those will never touch the db. Then I’d have “tests/functional” or “tests/integration” that contain tests the will test against a real db.

 

Sent from Mail for Windows 10

--
You received this message because you are subscribed to the Google Groups "Pester" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pester+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Chris Dent

unread,
Jun 1, 2016, 10:02:06 AM6/1/16
to craig buchanan, Pester
If it's integration testing wouldn't that need to have an assured connection to the database to have value?

If it's unit testing, should it really be making a call to a live database to test internal logic? That is, you might want to demonstrate handling of good or bad returns from the database, but that need not rely on the database responding to get there as both responses can be fabricated.

Personally I separate those processes such that I would never expect to require a connection to a database (live or otherwise) when executing code sanity tests.

You could always mock Write-Debug and change it into a stream which is trivial to capture or standard out (whether by assertion or testing return values from your function) :)

Chris

craig buchanan

unread,
Jun 1, 2016, 11:01:18 AM6/1/16
to Pester, cra...@gmail.com
I like the idea.  So, the folder structure would resemble:

+ Project X
+ Functions
- Get-Bar.ps1
- Get-Foo.ps1
+ Tests

  + Integration
      - Get-Bar.Tests.ps1
  - Get-Foo.Tests.ps1
    + Unit
- Get-Bar.Tests.ps1

- Get-Foo.Tests.ps1

How did you adjust the 'dot sourcing' logic of each test file to account for the non-standard directory structure?

Do you have a simple, but realistic, example of a function and its unit and integration tests?

Thanks.

Dave Wyatt

unread,
Jun 1, 2016, 12:30:12 PM6/1/16
to craig buchanan, Pester
That folder structure was the personal preference of the original author.  It's not set in stone.  :)

I just use relative paths.  So you'd have something like this:

   . $PSScriptRoot\..\..\Functions\Get-Bar.ps1

If you need to run the test on PSv2, you wouldn't use $PSScriptRoot, but you get the idea.

Matt Wrock

unread,
Jun 1, 2016, 12:33:32 PM6/1/16
to Dave Wyatt, craig buchanan, Pester

I tend to use psake (https://github.com/psake/psake) as a way to organize build and test tasks so I’d have something like:

 

Task {

    pushd "$baseDir"

    $pesterDir = "$env:ChocolateyInstall\lib\Pester"

    if($testName){

        exec {."$pesterDir\tools\bin\Pester.bat" $baseDir/Tests -testName $testName}

    }

    else{

        exec {."$pesterDir\tools\bin\Pester.bat" $baseDir/Tests }

    }

    popd

}

 

Task Integration-Test -depends Pack-Nuget, Create-ModuleZipForRemoting {

    pushd "$baseDir"

    $pesterDir = "$env:ChocolateyInstall\lib\Pester"

    if($testName){

        exec {."$pesterDir\tools\bin\Pester.bat" $baseDir/IntegrationTests -testName $testName}

    }

    else{

        exec {."$pesterDir\tools\bin\Pester.bat" $baseDir/IntegrationTests }

    }

    popd

craig buchanan

unread,
Jun 1, 2016, 2:31:50 PM6/1/16
to Pester, dlwya...@gmail.com, cra...@gmail.com
This answers my unstated question 'how do you choose to run unit or integration tests without having to disable tests?'.  Thanks.

craig buchanan

unread,
Jun 1, 2016, 3:09:00 PM6/1/16
to Pester, cra...@gmail.com
You're suggesting using this technique (Programmatically capture Verbose Output in a PowerShell variable)?

Example:

Get-Foo.ps1:

  Function Get-Foo {

    param(
      [int]$Id
    )

    $query = "SELECT * FROM table WHERE id=$Id"

    Write-Debug $query

    Invoke-SqlCmd -ServerInstance 'Server' -Query $query

  }

Get-Foo.Tests.ps1:

  Describe 'Get-Foo' {
    Context 'When DB server is not available' {
      Mock
Invoke-SqlCmd {
        return [PsCustomObject]
@{ Field0=1; Field1='ABC'; Field2='03/01/2016' }
      }
      $Id=1
      It 'Produces the expected query' {
        $expected = "SELECT * FROM table WHERE Id=$Id"
        # redirect DEBUG message to output stream
        $actual = $($Output =  Get-Foo -Id 1) 5>&1
        $actual | Should Be $expected
      }
    }
  }


Requires setting $DebugPreference to `Continue`:

  PS> $DebugPreference="Continue"

Is this correct?

Thanks.

Chris Dent

unread,
Jun 1, 2016, 3:23:42 PM6/1/16
to craig buchanan, Pester
Not so complex. I'm suggesting you could do something like this:

Mock Write-Debug {
    Write-Warning $Message
}

The Warning stream can be simply redirected using the WarningVariable parameter. Or you might do the same trick and use Write-Information if you're on PowerShell 5 (using the InformationVariable). No stream redirection (using the redirect operators) and it maintains separation from your standard output stream.

If you have a standard message format you might throw a parameter filter on top of that to give you greater control of when you trigger it. This might be especially useful if you're testing for a specific message at a specific point in your code.

For example:

Mock Write-Debug -ParameterFilter { $Message -like 'Exiting loop 1:*' } -MockWith {
    Write-Warning $Message
}

It 'Writes debug output when exiting loop 1' {
    Some-Function -WarningVariable SomeVar -WarningAction SilentlyContinue
    $SomeVar[-1] | Should Be 'Exiting loop 1: With debug output'
    Assert-MockCalled Write-Debug -ParameterFilter { $Message -like 'Exiting loop 1:*' } -Exactly 1 -Scope It
}

Full proof code:

function Some-Function {
    [CmdletBinding()]
    param( )

    Write-Debug 'Exiting loop 1: With debug output'
}

Describe 'Some test' {
    Context 'Debug rewrite' {
        Mock Write-Debug -ParameterFilter { $Message -like 'Exiting loop 1:*' } -MockWith {
            Write-Warning -Message $Message
        }

        It 'Writes debug output when exiting loop 1' {
            Some-Function -WarningVariable SomeVar -WarningAction SilentlyContinue
            $SomeVar | Should Be 'Exiting loop 1: With debug output'
            Assert-MockCalled Write-Debug -ParameterFilter { $Message -like 'Exiting loop 1:*' } -Exactly 1 -Scope It
        }
    }
}

craig buchanan

unread,
Jun 2, 2016, 3:11:34 PM6/2/16
to Pester, cra...@gmail.com
Makes sense.  Thanks for the help.
Reply all
Reply to author
Forward
0 new messages