Reputation: 19748
Here's some pseudo-code showing the function to test:
function Set-Something
{
if (Test-Something)
{
return $True
}
# Not found so do something to create it.
Do-Something
# Check it's been created successfully.
if (Test-Something)
{
return $True
}
return $False
}
This must be a fairly common pattern: "Test for existence - if not found create - test again to verify creation". Testing most branches is pretty simple but how can I test the branch where Test-Something fails the first time it's called, then succeeds the second time it's called?
This is my test code so far:
Describe 'Set-Something' {
Context 'already exists' {
Mock Test-Something { return $True }
Mock Do-Something
It 'returns True' {
{ Set-Something } | Should -Be $True
}
It 'does not call Do-Something' {
Set-Something
Assert-MockCalled Do-Something -Times 0 -Exactly
}
}
Context 'does not already exist and creation fails' {
Mock Test-Something { return $False }
Mock Do-Something
It 'calls Do-Something' {
Set-Something
Assert-MockCalled Do-Something -Times 1 -Exactly
}
It 'calls Test-Something twice' {
Set-Something
Assert-MockCalled Test-Something -Times 2 -Exactly
}
It 'returns False' {
{ Set-Something } | Should -Be $False
}
}
Context 'does not already exist and creation succeeds' {
Mock Test-Something { ?? }
Mock Do-Something
It 'calls Do-Something' {
Set-Something
Assert-MockCalled Do-Something -Times 1 -Exactly
}
It 'calls Test-Something twice' {
Set-Something
Assert-MockCalled Test-Something -Times 2 -Exactly
}
It 'returns True' {
{ Set-Something } | Should -Be $True
}
}
}
The case 'does not already exist and creation succeeds' is the problem. Test-Something needs to be mocked so it fails the first time it's called, and succeeds the second time. The arguments passed to Test-Something will be identical in each call so I can't use ParameterFilter to create two mocks of Test-Something with different behaviour.
Upvotes: 4
Views: 361
Reputation: 19748
I've found a couple of ways of mocking this:
1) Use a "static" (ie script-scoped) variable to record state
Context 'does not already exist and creation succeeds' {
BeforeEach {
$script:exists = $False
}
AfterAll {
Remove-Variable exists -Scope Script
}
Mock Test-Something {
return $script:exists
}
Mock Do-Something {
$script:exists = $True
}
It 'calls Do-Something' {
Set-Something
Assert-MockCalled Do-Something -Times 1 -Exactly
}
It 'calls Test-Something twice' {
Set-Something
Assert-MockCalled Test-Something -Times 2 -Exactly
}
It 'returns True' {
{ Set-Something } | Should -Be $True
}
}
2) Use a hash table to record state
Context 'does not already exist and creation succeeds' {
BeforeEach {
$mockState = @{
ItExists = $False
}
}
Mock Test-Something {
return $mockState.ItExists
}
Mock Do-Something {
$mockState.ItExists = $True
}
It 'calls Do-Something' {
Set-Something
Assert-MockCalled Do-Something -Times 1 -Exactly
}
It 'calls Test-Something twice' {
Set-Something
Assert-MockCalled Test-Something -Times 2 -Exactly
}
It 'returns True' {
{ Set-Something } | Should -Be $True
}
}
Personally, I like the hash table, because $mockState. ...
seems to me to indicate the purpose of the variable better than $script:...
. Also, a script-scoped variable could cause a race condition if the tests were ever parallelized and another Describe block modified the same variable.
Upvotes: 1