Skip to content

Add Run.BeforeInvoke bootstrap for Invoke-Pester#2772

Draft
nohwnd wants to merge 1 commit into
mainfrom
nohwnd-pester-before-invoke
Draft

Add Run.BeforeInvoke bootstrap for Invoke-Pester#2772
nohwnd wants to merge 1 commit into
mainfrom
nohwnd-pester-before-invoke

Conversation

@nohwnd

@nohwnd nohwnd commented Jun 27, 2026

Copy link
Copy Markdown
Member

Summary

Adds a Run.BeforeInvoke option that runs optional bootstrap code in the caller's scope, as soon as Invoke-Pester starts — before the caller's $PesterPreference is read and before discovery. Use it to import dependencies and to provide configuration by defining or modifying $PesterPreference, which Pester then picks up as the caller preference.

This mirrors the container-level BeforeContainer convention, but for the top-level invocation itself.

How it works

Two sources, in priority order:

  1. Run.BeforeInvoke config option (scriptblock[]). When set, it wins and runs as-is.
  2. Convention file — the first Pester.BeforeInvoke.ps1 found when walking up from each Run.Path toward Run.RepoRoot is dot-sourced (deduped, in order, never escaping the repo root).

Each scriptblock is bound to the caller's SessionState and dot-sourced, so imported modules, defined functions, and $PesterPreference land in the caller's scope where Invoke-Pester reads them next. The bootstrap runs for top-level runs only, so nested Pester-in-Pester does not re-run it.

Changes

  • C#BeforeInvoke ScriptBlockArrayOption on RunConfiguration (mirrors RepoRoot/ScriptBlock).
  • src/functions/Pester.BeforeInvoke.ps1Resolve-PesterBeforeInvoke + Invoke-PesterBeforeInvoke.
  • Main.ps1 — resolves a preliminary preference and runs the bootstrap before the caller-preference read.
  • Help — generated BeforeInvoke entry in about_PesterConfiguration.
  • Teststst/Pester.BeforeInvoke.ts.ps1 (9 P-tests). Full suite green.

Open design question (reason this is a draft)

BeforeInvoke currently runs once, in the orchestrating Invoke-Pester call. For a parallel run, test containers execute in separate runspaces that would not have re-run the bootstrap — so dependency setup done here would be missing in the workers. Configuration ($PesterPreference) is inherently a once-per-invocation concern and is fine, but dependency provisioning may need to also happen per-container (e.g. via BeforeContainer) for parallel scenarios. Want to settle this before merging.

true

Introduce a Run.BeforeInvoke option that runs optional bootstrap code in
the caller's scope as soon as Invoke-Pester starts, before the caller's
$PesterPreference is read and before discovery. Use it to import
dependencies and to provide configuration by defining or modifying
$PesterPreference, which Pester then picks up as the caller preference.

Two sources, mirroring the container-level convention:

- Run.BeforeInvoke config option (scriptblock[]). When set, it wins.
- Convention file: the first Pester.BeforeInvoke.ps1 found when walking up
  from each Run.Path towards Run.RepoRoot is dot-sourced (deduped, in
  order, never escaping the repo root).

Each scriptblock is bound to the caller's SessionState and dot-sourced so
imports, defined functions and $PesterPreference land in the caller's
scope. Bootstrap runs for top-level runs only, so nested Pester-in-Pester
does not re-run it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@nohwnd nohwnd marked this pull request as draft June 27, 2026 19:26
@johlju

johlju commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

I really like this, looking forward to this merging. I will reduce a lot of duplicated code in test files by having a Pester.BeforeInvoke.ps1 that can contain bootstrap code. We can put one with different content in folders qa, unit and integration.

If it would go up to the repo root and find all and run them in sequence we could even reduce even more duplication, for example

reporoot
  |
  | -- tests    <--- finds Pester.BeforeInvoke.ps1
           |
           | -- unit
           | -- integration     <--- finds Pester.BeforeInvoke.ps1

It would then invoke them in the order top to bottom:

  1. ./tests/Pester.BeforeInvoke.ps1
  2. ./tests/integration/Pester.BeforeInvoke.ps1

But just having the option of one Pester.BeforeInvoke.ps1 would probably reduce the duplication with ~1000 rows in a larger project (like in SqlServerDsc).

@nohwnd

nohwnd commented Jun 28, 2026

Copy link
Copy Markdown
Member Author

@johlju -rc1 is out, it has similar feature, BeforeContainer, my main problem is that we cannot just promise that it will run once, because if you need to run tests in parallel (there is experimental parallel mode), this script needs to run in every new runspace. And I wanted to minimize the overhead of looking the file up every time, and did not want to think about how to say "this is in child folder, but don't run any of the script files you find above me" like "root=true" in .editorconfig.

but all those are good suggestions, show me how they simplify your workflow, and we could at least add them as options, if not as defult. I just did not want to over design and over promise in the initial release.

@johlju

johlju commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

It says in the release notes för rc1:

"...single Pester.BeforeContainer.ps1 in the repository root..."
"... run before every test file is discovered and run..."

Assuming we can pass BeforeDiscovery, BeforeAll and AfterAll in this file it might get us a a fair bit. Some things do not run during Discovery and some things do not run during Run. Some code are duplicate, but we do slightly different setup for different things:

  • unit tests of private and public command
  • Integration tests of public commands
  • Unit tests for class-based DSC resource
  • Integration tests for class-based DSC resource
  • Unit tests for MOF-based DSC resource
  • Integration tests for MOF-based DSC resource

A single file would not handle different setup unless it can pass current test script file so the code Pester.BeforeContainer.ps1 can determine what is about to be tested.

But I will check how far the current functionality takes us and report the GAP. If we can pass BeforeDiscovery, BeforeAll and AfterAll in Pester.BeforeContainer.ps1 that will potentially reduce most duplicated code.
If it does not support BeforeDiscovery, BeforeAll and AfterAll as the release notes might suggest, then we'll see what we can put there, at least some of the BeforeDiscovery part could be moved... 🤔

@nohwnd

nohwnd commented Jun 28, 2026

Copy link
Copy Markdown
Member Author

Assuming we can pass BeforeDiscovery, BeforeAll and AfterAll

I actually don't know. I did not think about that, my thinking here was that it imports modules you need, adds helper functions etc. stuff that I typically do in a "test.ps1" script. But that now needs to happen in every child runspace.

All in all the idea of what I want to achieve here is not super clear even to me. My goal is also that I can run tests (e.g. a single It) from VSCode that need some setup, and not repeat the setup on top of every file.

A single file would not handle different setup unless it can pass current test script file so the code Pester.BeforeContainer.ps1 can determine what is about to be tested.

You could probably do that through the power of powershell, e.g. by looking at your pscall stack, but I get what you mean.

@johlju

johlju commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

My goal is also that I can run tests (e.g. a single It) from VSCode that need some setup, and not repeat the setup on top of every file.

That is why there are extensive bootstrap in each file in SqlServerDsc - so there our goal ids aligned 🙂 also I need to use Invoke-PesterJob in VS Code (and outside) for tests if they load classes so I do not need to kill the session each time we re-run test.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants