Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 59 additions & 17 deletions src/Pester.Runtime.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ else {
# 'New-EachTestTeardown'
# 'New-OneTimeTestSetup'
# 'New-OneTimeTestTeardown'
# 'New-EachBlockSetup'
# 'New-EachBlockTeardown'
# 'New-OneTimeBlockSetup'
# 'New-OneTimeBlockTeardown'
# 'New-EachDescribeSetup'
# 'New-EachContextSetup'
# 'New-EachDescribeTeardown'
# 'New-EachContextTeardown'
# 'Invoke-Test',
# 'Find-Test',
# 'Invoke-PluginStep'
Expand Down Expand Up @@ -390,7 +390,20 @@ function Invoke-Block ($previousBlock) {
-ScriptBlock $sb `
-OuterSetup @(
$(if (-not (Is-Discovery) -and (-not $Block.Skip)) {
@($previousBlock.EachBlockSetup) + @($block.OneTimeTestSetup)
# BeforeEach -Describe / -Context: walk up the ancestors and collect the
# each-block setup that matches how this block was defined, so a setup on
# an outer block runs before every matching nested block.
$__b = $previousBlock
$__eachBlockSetups = while ($null -ne $__b) {
if ('Describe' -eq $block.FrameworkData.CommandUsed) { $__b.EachDescribeSetup }
elseif ('Context' -eq $block.FrameworkData.CommandUsed) { $__b.EachContextSetup }
$__b = $__b.Parent
}
if ($null -ne $__eachBlockSetups -and 1 -lt @($__eachBlockSetups).Count) {
# collected inner-first, reverse so the outermost block runs first
[Array]::Reverse($__eachBlockSetups)
}
@($__eachBlockSetups) + @($block.OneTimeTestSetup)
})
$(if (-not $Block.IsRoot) {
# expand block name by evaluating the <> templates, only match templates that have at least 1 character and are not escaped by `<abc`>
Expand Down Expand Up @@ -427,7 +440,15 @@ function Invoke-Block ($previousBlock) {
})
) `
-OuterTeardown $( if (-not (Is-Discovery) -and (-not $Block.Skip)) {
@($block.OneTimeTestTeardown) + @($previousBlock.EachBlockTeardown)
# AfterEach -Describe / -Context: mirror of the setup walk above, run inner-first
# (opposite order of the setups) after the block's own AfterAll.
$__b = $previousBlock
$__eachBlockTeardowns = while ($null -ne $__b) {
if ('Describe' -eq $block.FrameworkData.CommandUsed) { $__b.EachDescribeTeardown }
elseif ('Context' -eq $block.FrameworkData.CommandUsed) { $__b.EachContextTeardown }
$__b = $__b.Parent
}
@($block.OneTimeTestTeardown) + @($__eachBlockTeardowns)
} ) `
-Context $context `
-MoveBetweenScopes `
Expand Down Expand Up @@ -842,38 +863,59 @@ function New-OneTimeTestTeardown {
}
}

# endpoint for adding a setup for each block in the current block
function New-EachBlockSetup {
# endpoint for adding a setup for each Describe block nested in the current block
function New-EachDescribeSetup {
param (
[Parameter(Mandatory = $true)]
[ScriptBlock] $ScriptBlock
)
if (Is-Discovery) {
$state.CurrentBlock.EachBlockSetup = $ScriptBlock
if ($null -ne $state.CurrentBlock.EachDescribeSetup) {
throw "BeforeEach -Describe is already defined in this block. Each block can only have one BeforeEach -Describe. Combine the code into a single block."
}
$state.CurrentBlock.EachDescribeSetup = $ScriptBlock
}
}

# endpoint for adding a teardown for each block in the current block
function New-EachBlockTeardown {
# endpoint for adding a setup for each Context block nested in the current block
function New-EachContextSetup {
param (
[Parameter(Mandatory = $true)]
[ScriptBlock] $ScriptBlock
)
if (Is-Discovery) {
$state.CurrentBlock.EachBlockTeardown = $ScriptBlock
if ($null -ne $state.CurrentBlock.EachContextSetup) {
throw "BeforeEach -Context is already defined in this block. Each block can only have one BeforeEach -Context. Combine the code into a single block."
}
$state.CurrentBlock.EachContextSetup = $ScriptBlock
}
}

# endpoint for adding a setup for all blocks in the current block
# endpoint for adding a teardown for all clocks in the current block
function New-OneTimeBlockTeardown {
[CmdletBinding()]
# endpoint for adding a teardown for each Describe block nested in the current block
function New-EachDescribeTeardown {
param (
[Parameter(Mandatory = $true)]
[ScriptBlock] $ScriptBlock
)
if (Is-Discovery) {
if ($null -ne $state.CurrentBlock.EachDescribeTeardown) {
throw "AfterEach -Describe is already defined in this block. Each block can only have one AfterEach -Describe. Combine the code into a single block."
}
$state.CurrentBlock.EachDescribeTeardown = $ScriptBlock
}
}

# endpoint for adding a teardown for each Context block nested in the current block
function New-EachContextTeardown {
param (
[Parameter(Mandatory = $true)]
[ScriptBlock] $ScriptBlock
)
if (Is-Discovery) {
$state.CurrentBlock.OneTimeBlockTeardown = $ScriptBlock
if ($null -ne $state.CurrentBlock.EachContextTeardown) {
throw "AfterEach -Context is already defined in this block. Each block can only have one AfterEach -Context. Combine the code into a single block."
}
$state.CurrentBlock.EachContextTeardown = $ScriptBlock
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/csharp/Pester/Block.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ public Block()
public ScriptBlock OneTimeTestSetup { get; set; }
public ScriptBlock EachTestTeardown { get; set; }
public ScriptBlock OneTimeTestTeardown { get; set; }
public ScriptBlock EachBlockSetup { get; set; }
public ScriptBlock OneTimeBlockSetup { get; set; }
public ScriptBlock EachBlockTeardown { get; set; }
public ScriptBlock OneTimeBlockTeardown { get; set; }
public ScriptBlock EachDescribeSetup { get; set; }
public ScriptBlock EachContextSetup { get; set; }
public ScriptBlock EachDescribeTeardown { get; set; }
public ScriptBlock EachContextTeardown { get; set; }
public List<object> Order { get; set; } = new List<object>();

public bool Passed { get; set; }
Expand Down
42 changes: 38 additions & 4 deletions src/functions/SetupTeardown.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,29 @@
[Parameter(Mandatory = $true,
Position = 1)]
[Scriptblock]
$Scriptblock
$Scriptblock,

# Run before each test (It) in the current block and its descendants. This is the default
# when no target switch is provided.
[switch] $It,

# Run before each Describe block nested in the current block.
[switch] $Describe,

# Run before each Context block nested in the current block.
[switch] $Context
)
Assert-DescribeInProgress -CommandName BeforeEach
Assert-BoundScriptBlockInput -ScriptBlock $Scriptblock

New-EachTestSetup -ScriptBlock $Scriptblock
# keep the classic per-test behavior when no target switch is given
if (-not ($It -or $Describe -or $Context)) {
$It = $true
}

if ($It) { New-EachTestSetup -ScriptBlock $Scriptblock }
if ($Describe) { New-EachDescribeSetup -ScriptBlock $Scriptblock }
if ($Context) { New-EachContextSetup -ScriptBlock $Scriptblock }
}

function AfterEach {
Expand Down Expand Up @@ -121,12 +138,29 @@ function AfterEach {
[Parameter(Mandatory = $true,
Position = 1)]
[Scriptblock]
$Scriptblock
$Scriptblock,

# Run after each test (It) in the current block and its descendants. This is the default
# when no target switch is provided.
[switch] $It,

# Run after each Describe block nested in the current block.
[switch] $Describe,

# Run after each Context block nested in the current block.
[switch] $Context
)
Assert-DescribeInProgress -CommandName AfterEach
Assert-BoundScriptBlockInput -ScriptBlock $Scriptblock

New-EachTestTeardown -ScriptBlock $Scriptblock
# keep the classic per-test behavior when no target switch is given
if (-not ($It -or $Describe -or $Context)) {
$It = $true
}

if ($It) { New-EachTestTeardown -ScriptBlock $Scriptblock }
if ($Describe) { New-EachDescribeTeardown -ScriptBlock $Scriptblock }
if ($Context) { New-EachContextTeardown -ScriptBlock $Scriptblock }
}

function BeforeAll {
Expand Down
45 changes: 2 additions & 43 deletions tst/Pester.Runtime.ts.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1296,47 +1296,6 @@ i -PassThru:$PassThru {
$container.OneTimeTestTeardown | Verify-Equal 1
}

t "block setups&teardowns run only when there are some tests to run in the block" {
$container = [PSCustomObject]@{
OneTimeBlockSetup1 = 0
EachBlockSetup1 = 0
EachBlockTeardown1 = 0
OneTimeBlockTeardown1 = 0
}
$actual = Invoke-Test -SessionState $ExecutionContext.SessionState -BlockContainer (New-BlockContainerObject -ScriptBlock {

# New-OneTimeBlockSetup { $container.OneTimeBlockSetup1++}
New-EachBlockSetup {
$container.EachBlockSetup1++
}

New-Block 'block1' {
New-Test "test1" {
"here"
}
}

New-Block 'no test block' {

}

New-Block 'no running tests' {
New-Test "do not run test" -Tag "DoNotRun" {
}
}

New-EachBlockTeardown {
$container.EachBlockTeardown1++
}
# New-OneTimeBlockTeardown { $container.OneTimeBlockTeardown1++ }
}) -Filter (New-FilterObject -ExcludeTag DoNotRun)

# $container.OneTimeBlockSetup1 | Verify-Equal 1
$container.EachBlockSetup1 | Verify-Equal 1
$container.EachBlockTeardown1 | Verify-Equal 1
# $container.OneTimeBlockTeardown1 | Verify-Equal 1
}

t 'setup and teardown are executed on skipped parent blocks when a test is explicitly included' {
$container = @{
OneTimeTestSetup = 0
Expand Down Expand Up @@ -2262,7 +2221,7 @@ i -PassThru:$PassThru {
$Value | Verify-Equal 1
}

New-OneTimeBlockTeardown {
New-OneTimeTestTeardown {
$Value | Verify-Equal 1
}

Expand All @@ -2289,7 +2248,7 @@ i -PassThru:$PassThru {
$Color = "Blue"
}

New-OneTimeBlockTeardown {
New-OneTimeTestTeardown {
$Value | Verify-Equal 1
}

Expand Down
81 changes: 81 additions & 0 deletions tst/functions/SetupTeardown.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,84 @@ Describe 'Duplicate setup and teardown blocks throw' {
#}

#Testing if failing setup or teardown will fail 'It' is done in the Pester.Runtime.ts.ps1 file ("failing one time block test setups and teardowns")

Describe 'BeforeEach/AfterEach -Context and -Describe (#1219)' {
BeforeAll {
$blockScoped = @{
Order = [System.Collections.Generic.List[string]]::new()
InheritedSetup = 0
DescribeSetup = 0
Combined = [System.Collections.Generic.List[string]]::new()
}
}

Describe 'a -Context setup and teardown wrap each Context' {
BeforeEach -Context { $blockScoped.Order.Add('setup') }
AfterEach -Context { $blockScoped.Order.Add('teardown') }

Context 'A' {
It 'a' { $blockScoped.Order.Add('A') }
}

Context 'B' {
It 'b' { $blockScoped.Order.Add('B') }
}
}

Describe 'the recorded -Context order' {
It 'ran the setup before and the teardown after each Context, in order' {
($blockScoped.Order -join ',') | Should -Be 'setup,A,teardown,setup,B,teardown'
}
}

Describe 'a -Context setup does not run for a test directly in the block' {
BeforeEach -Context { $blockScoped.DirectContextSetupRan = $true }

It 'a test that is not inside a Context is not wrapped' {
$blockScoped.ContainsKey('DirectContextSetupRan') | Should -BeFalse
}
}

Describe 'a -Context setup is inherited by nested Contexts' {
BeforeEach -Context { $blockScoped.InheritedSetup++ }

Context 'outer' {
Context 'inner' {
It 'ran the inherited setup entering both the outer and the inner Context' {
$blockScoped.InheritedSetup | Should -Be 2
}
}
}
}

Describe 'a -Describe setup targets Describe blocks, not Context blocks' {
BeforeEach -Describe { $blockScoped.DescribeSetup++ }

Context 'a context' {
It 'did not run the -Describe setup for a Context' {
$blockScoped.DescribeSetup | Should -Be 0
}
}

Describe 'a nested describe' {
It 'ran the -Describe setup for a nested Describe' {
$blockScoped.DescribeSetup | Should -Be 1
}
}
}

Describe 'a -Context -It setup runs before the Context and before each test in it' {
BeforeEach -Context -It { $blockScoped.Combined.Add('setup') }

Context 'c' {
It 'first' { $blockScoped.Combined.Add('first') }
It 'second' { $blockScoped.Combined.Add('second') }
}
}

Describe 'the recorded -Context -It order' {
It 'ran once entering the Context and once before each test' {
($blockScoped.Combined -join ',') | Should -Be 'setup,setup,first,setup,second'
}
}
}
Loading