Skip to content

Instantly share code, notes, and snippets.

@nohwnd
Last active April 22, 2021 12:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nohwnd/5b415fb10ff29f830ac8e98c85eee145 to your computer and use it in GitHub Desktop.
Save nohwnd/5b415fb10ff29f830ac8e98c85eee145 to your computer and use it in GitHub Desktop.
Mock interaction
# 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
Copy link

fflaten commented Apr 21, 2021

@nohwnd The hardest thing about this is the complete confusion below. Add this at the end of the It-block

Write-Host "we call f directly from script. while expecting it use script mock, it resolves to mock in o:"
f

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?

@nohwnd
Copy link
Author

nohwnd commented Apr 22, 2021

@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 }

@nohwnd
Copy link
Author

nohwnd commented Apr 22, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment