Pester 5: Discovery and TestCases

Pester 5 was released a couple of days ago which brings a lot of new features and changes to how we write tests. One of the major changes in Pester 5 is the Discovery phase. In this post I will attempt to explain how Discovery works and how to use it to generate test cases.

Tests are executed in two phases

Whenever you run tests with Pester 5 there will always be two phases, the Discovery phase and the Run phase. The Discovery phase, as the name implies, tries to discover which tests to run and prepare those tests so that they are ready when the next phase starts. The Run phase will execute the actual tests and evaluate the results.

Phase isolation

Its important to understand that each of these phases are isolated from each other. Variables generated in the Discovery phase, for example, are not generally available in the Run phase. This is what stumbles a lot of people when they are getting started with Pester 5. Think of the two phases as two separate scripts which has little to do with each other. That's a very simplified way of explaining it, and Mr JareŇ° would probably say that its wrong, but thinking in that way makes it easier (at least for me) to understand it in the beginning. Once you wrap your mind around it, it makes a lot of sense.

What Discovery does

When you run a test, Pester will first start the discovery of your test script. During discovery Pester tries to figure out which tests should be executed. Once it has figured out which tests the next phase should run, it stores all the relevant script blocks from BeforeAll, BeforeEach, It, AfterAll and AfterEach. Now Pester is ready to execute the Run phase where the actual tests are processed.

What runs when

All code within the script blocks for BeforeAll, BeforeEach, It, AfterAll and AfterEach will be executed during the Run phase and not during Discovery. Notice that I mentioned script blocks? That's right, everything else will be executed during Discovery. Lets do an example.

Describe 'Discovery example' {
    Context 'Some tests' {
        BeforeAll {
            $ExpectedResult = 'true'
        }
        $MyVariable = 'true'
        It "$MyVariable is true" {
            'true' | Should -Be $ExpectedResult
        }
    }
}

This example would kind of look like this from a Discovery point of view:

Describe 'Discovery example' {
    Context 'Some tests' {
        BeforeAll { ... }
        $MyVariable = 'true'
        It "$MyVariable is true" { ... }
    }
}

The content of the script blocks for BeforeAll and It will not be executed, but everything else will. This has some interesting consequences. The variable $MyVariable will be processed during discovery. So will the name of the It block. The test itself will fail, because the $MyVariable has not been set inside either BeforeAll or It.

When discovery is completed, you could say that the remaining script to run for the Run phase would be the following:

Describe 'Discovery example' {
    Context 'Some tests' {
        BeforeAll {
            $ExpectedResult = 'true'
        }
        It "true is true" {
            'true' | Should -Be $ExpectedResult
        }
    }
}

Will this execute in Discovery or Run?

The following example shows what parts of a test script runs during the Discovery versus the Run phase.

# Discovery
BeforeAll {
    # Run
}
Describe 'Discovery' -Tag 'Discovery' -Skip:$Discovery {
    # Discovery
    BeforeAll {
        # Run
    }
    BeforeEach {
        # Run
    }
    Context 'Discovery' -Tag 'Discovery' -Skip:$Discovery {
        BeforeAll {
            # Run
        }
        BeforeEach {
            # Run
        }
        # Discovery
        It "Discovery" -Tag 'Discovery' -TestCases @{'Discovery' = 'Discovery'} -Skip:$Discovery {
            # Run
        }
        AfterEach {
            # Run
        }
        AfterAll {
            # Run
        }
    }
    AfterEach {
        # Run
    }
    AfterAll {
        # Run
    }
}
AfterAll {
    # Run
}
# Discovery

Generating tests with TestCases

If you want to dynamically generate tests in your scripts you have to write the script a bit different compared to Pester 4. Previously we could generate tests like this:

# Pester 4
Describe 'Pester 4 Example' {
    foreach ($number in 1..10) {
        It "$number is a number" {
            $number | Should -BeOfType [int]
        }
    }
}

This would generate 10 tests in Pester 4 and everything would work. But in Pester 5 this would not work since we now have the discovery phase and the $number parameter would not be available inside the It block during the Test phase. Instead you can use the TestCases parameter.

# Pester 5
Describe 'Pester 5 Example' {
    foreach ($number in 1..10) {
        It "$number is a number" -TestCases @{'digit' = $number} {
            $digit | Should -BeOfType [int]
        }
    }
}

The reason for this is that all the parameters for the It command is evaluated in Discovery, except the script block. The foreach loop is located directly in the Describe block which means that it will be processed during Discovery as well.

How TestCases work

Notice that the variable $digit just magically appeared in the script block for It in the last example? Let me try to explain what happened there.

The TestCases parameter accepts a hash table. When the script block executes Pester will create variables for each key in the hash table. The following example shows how two variables are created from the TestCases parameter.

$tests = @{
    'First'  = 'word'
    'Second' = 'word'
}

It "first and second is the same" -TestCases $tests {
    $First | Should -Be $Second
}

Understanding discovery is a major part of understanding Pester 5. I hope this post has helped answer a few questions.

No comments

Post a Comment