microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
a3acef32dec8d8ac8051793df3686007a92266cd

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/instructions/coding-standards/powershell/pester.instructions.md

315lines · modecode

1---
2description: "Instructions for Pester testing conventions - Brought to you by microsoft/hve-core"
3applyTo: '**/*.Tests.ps1'
4---
5
6# Pester Testing Instructions
7
8Pester 5.x is the testing framework for all PowerShell code. Tests run exclusively through `npm run test:ps`. Never invoke Pester or test scripts directly.
9
10## Test File Naming
11
12Test files use a `.Tests.ps1` suffix matching the production file name:
13
14| Production file | Test file |
15|------------------------------|------------------------------------|
16| `Test-DependencyPinning.ps1` | `Test-DependencyPinning.Tests.ps1` |
17| `SecurityHelpers.psm1` | `SecurityHelpers.Tests.ps1` |
18| `SecurityClasses.psm1` | `SecurityClasses.Tests.ps1` |
19
20## Test File Location
21
22**Mirror directory pattern**: Test files in `scripts/tests/` mirror the production `scripts/` layout. Each production subdirectory has a corresponding test subdirectory:
23
24| Production directory | Test directory |
25|------------------------|------------------------------|
26| `scripts/collections/` | `scripts/tests/collections/` |
27| `scripts/linting/` | `scripts/tests/linting/` |
28| `scripts/security/` | `scripts/tests/security/` |
29| `scripts/lib/` | `scripts/tests/lib/` |
30
31**Co-located skill tests**: Skills place tests inside the skill directory rather than the mirror tree:
32
33```text
34.github/skills/shared/pr-reference/
35├── scripts/
36│ ├── generate.ps1
37│ └── shared.psm1
38└── tests/
39 ├── generate.Tests.ps1
40 └── shared.Tests.ps1
41```
42
43## Test File Header
44
45Test files place `#Requires -Modules Pester` before the copyright header. This ordering differs from production scripts:
46
47```powershell
48#Requires -Modules Pester
49# Copyright (c) Microsoft Corporation.
50# SPDX-License-Identifier: MIT
51```
52
53## SUT Import Patterns
54
55Import the system under test in a file-level `BeforeAll` block. Use the pattern matching the production file type:
56
57**Dot-source for scripts** (`.ps1`):
58
59```powershell
60BeforeAll {
61 . (Join-Path $PSScriptRoot '../../security/Test-DependencyPinning.ps1')
62}
63```
64
65**Import-Module for modules** (`.psm1`):
66
67```powershell
68BeforeAll {
69 Import-Module (Join-Path $PSScriptRoot '../../security/Modules/SecurityHelpers.psm1') -Force
70}
71```
72
73**using module for class modules** (parse-time type resolution):
74
75```powershell
76using module ..\..\security\Modules\SecurityClasses.psm1
77```
78
79The `using module` statement appears at the top of the file outside any block because PowerShell processes it at parse time.
80
81## BeforeAll Setup
82
83File-level `BeforeAll` initializes the test environment. Common activities include SUT import, mock module import, fixture path resolution, and output suppression:
84
85```powershell
86BeforeAll {
87 . (Join-Path $PSScriptRoot '../../security/Test-DependencyPinning.ps1')
88 Import-Module (Join-Path $PSScriptRoot '../../security/Modules/SecurityHelpers.psm1') -Force
89 Import-Module (Join-Path $PSScriptRoot '../Mocks/GitMocks.psm1') -Force
90 $script:FixtureRoot = Join-Path $PSScriptRoot '../Fixtures/Security'
91 Mock Write-Host {}
92 Mock Write-CIAnnotation {} -ModuleName SecurityHelpers
93}
94```
95
96## Describe, Context, and It Blocks
97
98All `Describe` blocks require `-Tag 'Unit'`. The pester configuration excludes `Integration` and `Slow` tags by default:
99
100```powershell
101Describe 'FunctionName' -Tag 'Unit' {
102 Context 'when input is valid' {
103 It 'Returns expected output' {
104 Get-Something -Path 'test' | Should -Be 'result'
105 }
106 }
107}
108```
109
110`Context` groups related scenarios. Each `It` tests a single behavior with a descriptive sentence name.
111
112## Data-Driven Tests
113
114Use `-ForEach` on `It` blocks for parameterized testing:
115
116```powershell
117It 'Accepts valid type <Value>' -ForEach @(
118 @{ Value = 'Unpinned' }
119 @{ Value = 'Stale' }
120 @{ Value = 'VersionMismatch' }
121) {
122 $v = [DependencyViolation]::new()
123 $v.ViolationType = $Value
124 $v.ViolationType | Should -Be $Value
125}
126```
127
128## Mock Patterns
129
130**Output suppression**: Empty scriptblock mocks prevent console noise:
131
132```powershell
133Mock Write-Host {}
134```
135
136**Module-scoped mocks**: `-ModuleName` injects mocks into modules under test:
137
138```powershell
139Mock Write-CIAnnotation {} -ModuleName SecurityHelpers
140```
141
142**Parameter-filtered mocks**: `-ParameterFilter` targets specific invocations:
143
144```powershell
145Mock git {
146 $global:LASTEXITCODE = 0
147 return 'abc123'
148} -ModuleName LintingHelpers -ParameterFilter {
149 $args[0] -eq 'merge-base'
150}
151```
152
153## Mock Verification
154
155Use `Should -Invoke` to verify mock calls:
156
157```powershell
158Should -Invoke Write-CIAnnotation -ModuleName SecurityHelpers -Times 1 -Exactly
159Should -Invoke Write-CIAnnotation -ModuleName SecurityHelpers -ParameterFilter {
160 $Level -eq 'Warning'
161}
162```
163
164## Test Isolation
165
166**`$TestDrive`**: Pester-managed temp directory, automatically cleaned per `Describe`:
167
168```powershell
169$testDir = Join-Path $TestDrive 'test-collection'
170New-Item -ItemType Directory -Path $testDir -Force
171```
172
173**`New-TemporaryFile` with try/finally**: Manual temp file management when `$TestDrive` is insufficient:
174
175```powershell
176$tempFile = New-TemporaryFile
177try {
178 # test using $tempFile
179}
180finally {
181 Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
182}
183```
184
185**`$script:` scope**: Shares state across `It` blocks within a `Describe` or `Context`:
186
187```powershell
188BeforeAll {
189 $script:result = Get-Something -Path 'test'
190}
191It 'Returns correct name' {
192 $script:result.Name | Should -Be 'expected'
193}
194```
195
196## Environment Save and Restore
197
198Tests modifying environment variables use the `GitMocks.psm1` save/restore pattern:
199
200```powershell
201BeforeAll {
202 Import-Module "$PSScriptRoot/../Mocks/GitMocks.psm1" -Force
203}
204BeforeEach {
205 Save-CIEnvironment
206 $script:MockFiles = Initialize-MockCIEnvironment
207}
208AfterEach {
209 Remove-MockCIFiles -MockFiles $script:MockFiles
210 Restore-CIEnvironment
211}
212```
213
214## Cleanup
215
216Remove imported modules in `AfterAll` to prevent state leakage between test files:
217
218```powershell
219AfterAll {
220 Remove-Module SecurityHelpers -Force -ErrorAction SilentlyContinue
221 Remove-Module GitMocks -Force -ErrorAction SilentlyContinue
222}
223```
224
225## Assertion Reference
226
227| Assertion | Usage |
228|-------------------------------|-------------------------|
229| `Should -Be` | Exact value equality |
230| `Should -BeExactly` | Case-sensitive equality |
231| `Should -BeTrue` / `-BeFalse` | Boolean checks |
232| `Should -BeNullOrEmpty` | Null or empty string |
233| `Should -Not -BeNullOrEmpty` | Non-null and non-empty |
234| `Should -Match` | Regex matching |
235| `Should -BeLike` | Wildcard matching |
236| `Should -Contain` | Collection membership |
237| `Should -BeOfType` | Type assertion |
238| `Should -HaveCount` | Collection length |
239| `Should -Throw` | Exception expected |
240| `Should -Not -Throw` | No exception expected |
241| `Should -BeGreaterThan` | Numeric comparison |
242| `Should -BeLessThan` | Numeric comparison |
243| `Should -Invoke` | Mock call verification |
244
245## Running Tests
246
247Run all tests:
248
249```bash
250npm run test:ps
251```
252
253Run tests for a specific directory or file:
254
255```bash
256npm run test:ps -- -TestPath "scripts/tests/linting/"
257npm run test:ps -- -TestPath "scripts/tests/security/Test-DependencyPinning.Tests.ps1"
258```
259
260After execution, check `logs/pester-summary.json` for overall status and `logs/pester-failures.json` for failure details.
261
262## Complete Test Example
263
264<!-- <template-complete-test> -->
265
266```powershell
267#Requires -Modules Pester
268# Copyright (c) Microsoft Corporation.
269# SPDX-License-Identifier: MIT
270
271BeforeAll {
272 . (Join-Path $PSScriptRoot '../../linting/Invoke-Linter.ps1')
273 Import-Module (Join-Path $PSScriptRoot '../Mocks/GitMocks.psm1') -Force
274 $script:FixtureRoot = Join-Path $PSScriptRoot '../Fixtures/Linting'
275 Mock Write-Host {}
276}
277
278Describe 'Invoke-Linter' -Tag 'Unit' {
279 Context 'when input file is valid' {
280 BeforeAll {
281 $script:result = Invoke-Linter -Path (Join-Path $script:FixtureRoot 'valid.md')
282 }
283
284 It 'Returns zero violations' {
285 $script:result.Violations | Should -HaveCount 0
286 }
287
288 It 'Sets status to pass' {
289 $script:result.Status | Should -Be 'Pass'
290 }
291 }
292
293 Context 'when input file has errors' {
294 BeforeAll {
295 $script:result = Invoke-Linter -Path (Join-Path $script:FixtureRoot 'invalid.md')
296 }
297
298 It 'Returns violations' {
299 $script:result.Violations | Should -Not -BeNullOrEmpty
300 }
301
302 It 'Includes file path in each violation' {
303 $script:result.Violations | ForEach-Object {
304 $_.File | Should -Not -BeNullOrEmpty
305 }
306 }
307 }
308}
309
310AfterAll {
311 Remove-Module GitMocks -Force -ErrorAction SilentlyContinue
312}
313```
314
315<!-- </template-complete-test> -->
316