Skip to content

Refactor: Remove InModuleScope wrapper from public function tests #567

@lipkau

Description

@lipkau

Background

In PR #549 (comment), @SrBlackVoid raised that wrapping entire public function test files in InModuleScope JiraPS { ... } is discouraged in Pester v5.

This is well-sourced and correct:

  • Pester v5 docs (Testing within Modules): "Avoid putting InModuleScope around your Describe and It blocks. InModuleScope prevents you from properly testing your published functions, does not ensure that your functions are actually published and slows down test discovery by loading the module."
  • Jakub Jares (core Pester maintainer) in pester/Pester#2109: "One case where you should avoid InModuleScope is when you are testing a public function [...] Mock -ModuleName <module> should be used instead."

Current State

  • All 60 public test files wrap everything in InModuleScope JiraPS { ... }
  • All 27 private test files also use InModuleScope (correct — that's what it's for)
  • Public tests already use -ModuleName JiraPS on most Mock and Should -Invoke calls, making the outer InModuleScope largely redundant

Why It Matters

When public function tests run inside InModuleScope:

  • They bypass the module export mechanism, so they don't verify functions are actually exported
  • They can accidentally access private state, masking integration issues
  • Test discovery is slower because the module is loaded during discovery
  • Tests don't reflect how consumers actually call the module

Migration Complexity

Three complications make this a standalone project:

  1. Write-MockDebugInfo dependency — Used in ~300+ mock scriptblocks across ~57 files. Currently dot-sourced inside InModuleScope, making it available in the module's session state where mocks execute. After migration, needs to be injected separately.
  2. ~16 Mock calls without -ModuleName — In 11 files. These work today only because they're inside InModuleScope. Each needs -ModuleName JiraPS added.
  3. ~160 Should -Invoke -CommandName calls without -ModuleName — In 20 files. Same issue.

Implementation Plan

Migration Pattern

Each public test file currently looks like:

BeforeDiscovery {
    . "$PSScriptRoot/../../Helpers/TestTools.ps1"
    Initialize-TestEnvironment
    $script:moduleToTest = Resolve-ModuleSource
    Import-Module $script:moduleToTest -Force -ErrorAction Stop
}

InModuleScope JiraPS {
    Describe "SomeFunction" -Tag 'Unit' {
        BeforeAll {
            . "$PSScriptRoot/../../Helpers/TestTools.ps1"
            # mocks, definitions...
        }
        # tests...
    }
}

After migration:

BeforeDiscovery {
    . "$PSScriptRoot/../../Helpers/TestTools.ps1"
    Initialize-TestEnvironment
    $script:moduleToTest = Resolve-ModuleSource
    Import-Module $script:moduleToTest -Force -ErrorAction Stop
}

Describe "SomeFunction" -Tag 'Unit' {
    BeforeAll {
        . "$PSScriptRoot/../../Helpers/TestTools.ps1"
        InModuleScope JiraPS { . "$PSScriptRoot/../../Helpers/TestTools.ps1" }
        # mocks (all with -ModuleName JiraPS), definitions...
    }
    # tests...
}

Per-File Checklist

For each of the 60 public test files:

  1. Remove the InModuleScope JiraPS { } wrapper around Describe
  2. Add InModuleScope JiraPS { . TestTools.ps1 } in BeforeAll to inject Write-MockDebugInfo into the module scope for mock scriptblocks
  3. Add -ModuleName JiraPS to any Mock calls missing it (~16 calls in 11 files)
  4. Add -ModuleName JiraPS to any Should -Invoke -CommandName calls missing it (~160 calls in 20 files)
  5. Run the test file individually to verify

Batching Strategy

Split into manageable PRs of ~10-15 files each, grouped by function area:

  • Batch 1: Add-Jira* functions (7 files)
  • Batch 2: Get-Jira* functions part 1 (10 files)
  • Batch 3: Get-Jira* functions part 2 (10 files)
  • Batch 4: Remove-Jira* functions (10 files)
  • Batch 5: Set-Jira* / Move-Jira* / Invoke-Jira* (7 files)
  • Batch 6: New-Jira* / Find-Jira* / Format-Jira / Resolve-JiraError (9 files)
  • Batch 7: Remaining files + final cleanup (7 files)

Out of Scope

  • Private function tests (Tests/Functions/Private/) — InModuleScope is appropriate there per Pester guidance, though narrowing to inside It blocks could be a separate effort
  • Changes to Write-MockDebugInfo itself or TestTools.ps1

Verification

After each batch: Invoke-Build -Task Clean, Build, Test must pass with 0 failures across all platforms (Windows PS5, Windows PS7, Ubuntu, macOS).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions