-
-
Save nohwnd/5b415fb10ff29f830ac8e98c85eee145 to your computer and use it in GitHub Desktop.
# ModuleName defines in which session state the mock will be effective, not in which module the function was defined. | |
# This is important when mocking functions exported from a module either when calling them from a script, or when calling | |
# them from a different module. In that case you want to define the mock in the place where the function is called from, | |
# not in the module where the function is defined. | |
# The only time you want to define the mock in the module where the function is defined is when you are testing an internal | |
# functions of the module (not shown here). | |
Invoke-Pester -Container ( | |
New-PesterContainer -ScriptBlock { | |
BeforeAll { | |
Get-Module m, n, o | Remove-Module | |
# We have 3 modules, m, n and o. We also have a function f that is defined in module m and n. | |
# The third module o internally imports the module n, and uses it's function f. | |
# This shows the interaction of the mocks when and what -ModuleName does. | |
New-Module -Name m -ScriptBlock { | |
function f () { | |
Write-Host "real f in m" | |
} | |
} | Import-Module | |
New-Module -Name n -ScriptBlock { | |
function f () { | |
Write-Host "real f in n" | |
} | |
} | Import-Module | |
New-Module -Name o -ScriptBlock { | |
function o () { | |
Write-Host "o calling f from n" | |
f | |
} | |
} | Import-Module | |
} | |
Describe "d" { | |
It "i" { | |
Write-Host "we call f from n, because both m and n export it, and n comes last so n\f shadows m\f" | |
Write-Host "when we don't use module qualified name:" | |
f | |
n\f | |
m\f | |
Write-Host "`nwe define a mock in script scope" | |
Mock f { Write-Host "Mock of f in script" } | |
Write-Host "f will resolve to the script mock:" | |
f | |
Write-Host "`we define a mock in module m" | |
Mock f -ModuleName m { Write-Host "Mock of f in m" } | |
Write-Host "f will still resolve to the script mock, or if there was none," | |
Write-Host "it would resolve to the real function exported from n:" | |
f | |
Write-Host "`nwe define a mock in module n" | |
Mock f -ModuleName n { Write-Host "Mock of f in n" } | |
Write-Host "f will still resolve to the script mock, or if there was none," | |
Write-Host "it would resolve to the real function exported from n:" | |
f | |
Write-Host "`nwe call f from inside of module m," | |
Write-Host "# it resolves to the mock defined in module m:" | |
InModuleScope m { f } | |
Write-Host "`nwe call f from inside of module n," | |
Write-Host "it resolves to the mock defined in module n:" | |
InModuleScope n { f } | |
Write-Host "`nwe call o which internally uses f. Module o is in the same situation as" | |
Write-Host "our script was originally, it cannot see any of the mocks because they are" | |
Write-Host "effective in other session states. So f resolves to the real f defined in n.:" | |
o | |
Write-Host "`nwe define mock of f in o" | |
Mock f -ModuleName o { Write-Host "Mock of f in o" } | |
Write-Host "o calls f, which resolves to the mock defined in o:" | |
o | |
} | |
} | |
}) -Output None # or use Diagnostic to see mock logging |
we call f from n, because both m and n export it, and n comes last so n\f shadows m\f | |
when we don't use module qualified name: | |
real f in n | |
real f in n | |
real f in m | |
we define a mock in script scope | |
f will resolve to the script mock: | |
Mock of f in script | |
we define a mock in module m | |
f will still resolve to the script mock, or if there was none, | |
it would resolve to the real function exported from n: | |
Mock of f in script | |
we define a mock in module n | |
f will still resolve to the script mock, or if there was none, | |
it would resolve to the real function exported from n: | |
Mock of f in script | |
we call f from inside of module m, | |
# it resolves to the mock defined in module m: | |
Mock of f in m | |
we call f from inside of module n, | |
it resolves to the mock defined in module n: | |
Mock of f in n | |
we call o which internally uses f. Module o is in the same situation as | |
our script was originally, it cannot see any of the mocks because they are | |
effective in other session states. So f resolves to the real f defined in n.: | |
o calling f from n | |
real f in n | |
we define mock of f in o | |
o calls f, which resolves to the mock defined in o: | |
o calling f from n | |
Mock of f in o |
@fflaten Good points. The behavior you describe is a bug imho. We should take note of the behaviors based on the hook that is used to invoke them. I am not sure why it is not done, either it is an oversight or it prevents some scenarios where mocks are leaking between modules from working. I think there is a fallback from the module to the main script when looking for the function, but that should still bind to the same hook, so if we consider the behaviors to be hook specific and not session state specific it should work fine.
# o calls the mock hook in o module, and that finds all the behaviors defined,
# o is the last one so it resolves to that
o
# f calls the mock hook that we defined in script (try get-command f here and in o module)
# they are different. But internally the behavior finder finds all the behaviors that were defined
# and uses the last default one -> the one from mock in o
f
# suprisingly this should be doing the same thing, the log shows that all 3 default behaviors
# were found but mock of f in m is used which is the correct behavior... odd...
InModuleScope m { f }
Yeah got it, I list all found behaviors, but after that filter them on ModuleName, when module name is not set (which for some reason it is not on o, and the script mock, but is set on m, and n) then they fall into the same bucket. So f from script incorrectly resolves to o. But calling f from m returns the correct behavior from mock in m.
@nohwnd The hardest thing about this is the complete confusion below. Add this at the end of the It-block
The mock in module o is also published in the script-scope as the original
function f
is not a part of module o. Bug?