Functions/Describe.Tests.ps1

Set-StrictMode -Version Latest

Describe 'Testing Describe' {
    It 'Has a non-mandatory fixture parameter which throws the proper error message if missing' {
        $command = Get-Command Describe -Module Pester
        $command | Should Not Be $null

        $parameter = $command.Parameters['Fixture']
        $parameter | Should Not Be $null

        # Some environments (Nano/CoreClr) don't have all the type extensions
        $attribute = $parameter.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
        $isMandatory = $null -ne $attribute -and $attribute.Mandatory

        $isMandatory | Should Be $false

        { Describe Bogus } | Should Throw 'No test script block is provided'
    }
}

InModuleScope Pester {
    Describe 'Describe - Implementation' {
        # Function / mock used for call history tracking and assertion purposes only.
        function MockMe { param ($Name) }
        Mock MockMe

        Context 'Handling errors in the Fixture' {
            $testState = New-PesterState -Path $TestDrive

            # This is necessary for now, Describe code assumes that filters should only apply at a stack depth of
            # "2". ("1" being the Tests.ps1 script that's active.)
            $testState.EnterTestGroup('Mocked Script', 'Script')

            $blockWithError = {
                throw 'Bad stuff happened!'
                MockMe
            }

            It 'Does not rethrow terminating exceptions from the Fixture block' {
                { DescribeImpl -Pester $testState -Name 'A test' -Fixture $blockWithError -NoTestDrive } | Should Not Throw
            }

            It 'Adds a failed test result when errors occur in the Describe block' {
                $testState.TestResult.Count | Should Not Be 0
                $testState.TestResult[-1].Passed | Should Be $false
                $testState.TestResult[-1].Name | Should Be 'Error occurred in Describe block'
                $testState.TestResult[-1].FailureMessage | Should Be 'Bad stuff happened!'
            }

            It 'Does not attempt to run the rest of the Describe block after the error occurs' {
                Assert-MockCalled MockMe -Scope Context -Exactly -Times 0
            }
        }

        Context 'Calls to the output blocks' {
            $testState = New-PesterState -Path $TestDrive

            # This is necessary for now, Describe code assumes that filters should only apply at a stack depth of
            # "2". ("1" being the Tests.ps1 script that's active.)
            $testState.EnterTestGroup('Mocked Script', 'Script')

            $describeOutput = { MockMe -Name Describe }
            $testOutput = { MockMe -Name Test }

            It 'Calls the Describe output block once, and does not call the test output block when no errors occur' {
                $block = { $null = $null }

                DescribeImpl -Pester $testState -Name 'A test' -Fixture $block -DescribeOutputBlock $describeOutput -TestOutputBlock $testOutput -NoTestDrive

                Assert-MockCalled MockMe -Exactly 0 -ParameterFilter { $Name -eq 'Test' } -Scope It
                Assert-MockCalled MockMe -Exactly 1 -ParameterFilter { $Name -eq 'Describe' } -Scope It
            }

            It 'Calls the Describe output block once, and the test output block once if an error occurs.' {
                $block = { throw 'up' }

                DescribeImpl -Pester $testState -Name 'A test' -Fixture $block -DescribeOutputBlock $describeOutput -TestOutputBlock $testOutput -NoTestDrive

                Assert-MockCalled MockMe -Exactly 1 -ParameterFilter { $Name -eq 'Test' } -Scope It
                Assert-MockCalled MockMe -Exactly 1 -ParameterFilter { $Name -eq 'Describe' } -Scope It
            }
        }

        Context 'Test Name Filter' {
            $testState = New-PesterState -Path $TestDrive -TestNameFilter '*One*', 'Test Two'

            # This is necessary for now, Describe code assumes that filters should only apply at a stack depth of
            # "2". ("1" being the Tests.ps1 script that's active.)
            $testState.EnterTestGroup('Mocked Script', 'Script')

            $testBlock = { MockMe }

            $cases = @(
                @{ Name = 'TestOneTest'; Description = 'matches a wildcard' }
                @{ Name = 'Test Two';    Description = 'matches exactly' }
                @{ Name = 'test two';    Description = 'matches ignoring case' }
            )

            It -TestCases $cases 'Calls the test block when the test name <Description>' {
                param ($Name)
                DescribeImpl -Name $Name -Pester $testState -Fixture $testBlock -NoTestDrive
                Assert-MockCalled MockMe -Scope It -Exactly 1
            }

            It 'Does not call the test block when the test name doesn''t match a filter' {
                DescribeImpl -Name 'Test On' -Pester $testState -Fixture $testBlock -NoTestDrive
                DescribeImpl -Name 'Two' -Pester $testState -Fixture $testBlock -NoTestDrive
                DescribeImpl -Name 'Bogus' -Pester $testState -Fixture $testBlock -NoTestDrive

                Assert-MockCalled MockMe -Scope It -Exactly 0
            }
        }

        Context 'Tags Filter' {
            $testState = New-PesterState -Path $TestDrive -TagFilter 'One', '*Two*'

            # This is necessary for now, Describe code assumes that filters should only apply at a stack depth of
            # "2". ("1" being the Tests.ps1 script that's active.)
            $testState.EnterTestGroup('Mocked Script', 'Script')

            $testBlock = { MockMe }

            $cases = @(
                @{ Tags = 'One';         Description = 'matches the first tag exactly' }
                @{ Tags = '*Two*';       Description = 'matches the second tag exactly' }
                @{ Tags = 'One', '*Two'; Description = 'matches both tags exactly' }
                @{ Tags = 'one';         Description = 'matches the first tag ignoring case' }
                @{ Tags = '*two*';       Description = 'matches the second tag ignoring case' }
            )

            It -TestCases $cases 'Calls the test block when the tag filter <Description>' {
                param ($Tags)

                DescribeImpl -Name 'Blah' -Tags $Tags -Pester $testState -Fixture $testBlock -NoTestDrive
                Assert-MockCalled MockMe -Scope It -Exactly 1
            }

            It 'Does not call the test block when the test tags don''t match the pester state''s tags.' {
                # Unlike the test name filter, tags are literal matches and not interpreted as wildcards.
                DescribeImpl -Name 'Blah' -Tags 'TestTwoTest' -Pester $testState -Fixture $testBlock -NoTestDrive

                Assert-MockCalled MockMe -Scope It -Exactly 0
            }
        }

        # Testing nested Describe is probably not necessary here; that's covered by PesterState.Tests.ps1 and $pester.EnterDescribe().
    }
}