This guide provides clear, step-by-step instructions for contributors on how to test changes locally before submitting a pull request. Following these practices ensures code quality and reduces review turnaround time.
- Prerequisites
- Test Framework Overview
- Running Tests Locally
- Writing Tests
- Code Coverage
- Test Checklist Before Submitting a PR
- Troubleshooting
Before running tests, ensure you have the following installed:
- PowerShell 7+ (recommended) or Windows PowerShell 5.1
- Pester (v5+) — the PowerShell testing framework
- PSScriptAnalyzer — for static code analysis
- The module built locally (see BUILD.md)
Install Pester and PSScriptAnalyzer if not already installed:
Install-Module -Name Pester -Scope CurrentUser -Force -SkipPublisherCheck
Install-Module -Name PSScriptAnalyzer -Scope CurrentUser -ForceMicrosoft Entra PowerShell uses the Pester testing framework. Tests are organized by module and cmdlet:
test/
├── Common-Functions.ps1 # Shared test utilities
├── Entra/ # v1.0 module tests
│ ├── Entra.Tests.ps1 # Test runner for all v1.0 tests
│ ├── Applications/ # Application cmdlet tests
│ ├── Users/ # User cmdlet tests
│ ├── Groups/ # Group cmdlet tests
│ ├── DirectoryManagement/ # Directory management tests
│ ├── Governance/ # Governance tests
│ ├── SignIns/ # Sign-in tests
│ ├── Reports/ # Report tests
│ └── Authentication/ # Authentication tests
└── EntraBeta/ # Beta module tests (same structure)
Each cmdlet has a corresponding .Tests.ps1 file (for example, Get-EntraUser.Tests.ps1).
To run the full test suite for the v1.0 module:
# 1. Build the module first (see build/BUILD.md)
# 2. Run all tests
cd entra-powershell
.\test\Entra\Entra.Tests.ps1For the Beta module:
.\test\EntraBeta\EntraBeta.Tests.ps1To run a single test file:
# Run tests for a specific cmdlet
Invoke-Pester -Path .\test\Entra\Users\Get-EntraUser.Tests.ps1 -Output DetailedTo run tests matching a specific name pattern:
# Run only tests with "Delete" in the name
Invoke-Pester -Path .\test\Entra\Users\ -TagFilter "Delete" -Output DetailedTo run all tests for a specific sub-module (e.g., Users):
Invoke-Pester -Path .\test\Entra\Users\ -Output DetailedFor more control, use a Pester configuration object:
$config = New-PesterConfiguration
$config.Run.Path = ".\test\Entra\Users\"
$config.Run.PassThru = $true
$config.Output.Verbosity = "Detailed"
$config.TestResult.Enabled = $true
$config.TestResult.OutputPath = ".\TestReport\TestResults.xml"
Invoke-Pester -Configuration $configEvery test file follows this structure:
# Copyright header
BeforeAll {
# Import the module under test
if ((Get-Module -Name Microsoft.Entra.Users) -eq $null) {
Import-Module Microsoft.Entra.Users
}
Import-Module (Join-Path $PSScriptRoot "..\..\Common-Functions.ps1") -Force
# Define mock data
$mockUser = {
return @( [PSCustomObject]@{
Id = "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb"
DisplayName = "Test User"
UserPrincipalName = "testuser@contoso.com"
})
}
# Set up mocks
Mock -CommandName Get-MgUser -MockWith $mockUser -ModuleName Microsoft.Entra.Users
Mock -CommandName Get-EntraContext -MockWith {
@{ Scopes = @("User.Read.All") }
} -ModuleName Microsoft.Entra.Users
}
Describe "Get-EntraUser" {
Context "Test for Get-EntraUser" {
It "Should return a user by ID" {
$result = Get-EntraUser -UserId "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb"
$result | Should -Not -BeNullOrEmpty
Should -Invoke -CommandName Get-MgUser -ModuleName Microsoft.Entra.Users -Times 1
}
It "Should fail when UserId is empty" {
{ Get-EntraUser -UserId } | Should -Throw "Missing an argument*"
}
}
}Tests must never make real API calls. Always use Pester mocks:
-
Mock all Microsoft Graph SDK calls — Use
Mock -CommandName <GraphCmdlet> -MockWith <ScriptBlock> -ModuleName <Module>for every Graph SDK cmdlet your code calls. -
Mock
Get-EntraContext— All cmdlets check for an active connection. Mock this to return a valid context:Mock -CommandName Get-EntraContext -MockWith { @{ Scopes = @("User.Read.All") } } -ModuleName Microsoft.Entra.Users
-
Test the not-connected scenario — Ensure your cmdlet fails gracefully when not connected:
It "should throw when not connected" { Mock -CommandName Get-EntraContext -MockWith { $null } -ModuleName Microsoft.Entra.Users { Get-EntraUser -UserId "test-id" } | Should -Throw "Not connected to Microsoft Graph*" }
-
Use placeholder IDs — Never use real resource IDs. Use the format
aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb. -
Use placeholder names and emails — Use fictitious names like
contoso.comdomains.
- Test file: Name it the same as the cmdlet —
<CmdletName>.Tests.ps1 - Describe block: Use the cmdlet name —
Describe "Get-EntraUser" - Context block: Group related scenarios —
Context "Test for Get-EntraUser" - It block: Use descriptive names starting with "Should" —
It "Should return all users when -All is specified"
Every cmdlet test should cover:
| Scenario | Example |
|---|---|
| Success with required parameters | It "Should return a user by ID" |
| Success with optional parameters | It "Should return top N users" |
| Parameter aliases | It "Should work with -Id alias" |
| Missing required parameters | It "Should fail when UserId is empty" |
| Invalid parameter values | It "Should fail when Top is not a number" |
| Not-connected state | It "Should throw when not connected" |
| Pipeline support (if applicable) | It "Should accept pipeline input" |
-All switch (if applicable) |
It "Should return all results with -All" |
The test runner is configured to target 100% code coverage. To generate a code coverage report:
$config = New-PesterConfiguration
$config.Run.Path = ".\test\Entra\Users\"
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = ".\module\Entra\Microsoft.Entra"
$config.CodeCoverage.CoveragePercentTarget = 100
$config.Output.Verbosity = "Detailed"
Invoke-Pester -Configuration $configCoverage reports are saved to the TestReport/ directory.
Before creating a pull request, verify the following:
- All existing tests pass — Run the full test suite and confirm zero failures.
- New cmdlets have corresponding tests — Every new cmdlet must have a
.Tests.ps1file. - No hardcoded values — Tests must not contain real resource IDs, display names, or tenant-specific data.
- No skipped tests — Do not skip existing tests (
-Skipis not allowed). - Mocks are used for all API calls — No real Microsoft Graph API calls are made during testing.
- Code coverage is maintained — New code should be fully covered by tests.
- PSScriptAnalyzer passes — Run
Invoke-ScriptAnalyzeron your changed files:Invoke-ScriptAnalyzer -Path .\module\Entra\<YourCmdlet>.ps1 -Severity Warning
Once all checks pass and your PR is ready, add the Ready For Review label to signal the team for review.
| Issue | Solution |
|---|---|
Pester not found |
Run Install-Module -Name Pester -Scope CurrentUser -Force -SkipPublisherCheck |
Function capacity 4096 exceeded |
Set $MaximumFunctionCount = 32768 before importing modules, or use PowerShell 7+ |
Execution policy error |
Run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser |
Module not found during tests |
Build the module first using .\build\Create-EntraModule.ps1 (see BUILD.md) |
Assembly already loaded |
Use a fresh PowerShell session — don't reuse sessions that have other module versions loaded |
Mock not applied |
Verify the -ModuleName parameter matches the module your cmdlet is in |
- Check the FAQ in BUILD.md
- Open a discussion
- File an issue if you believe there's a bug in the test infrastructure