Data driven tests
Migrating from Pester v4? Jump to Migrating from Pester v4.
Pester can generate tests based on data. This can range from providing multiple examples on a single It
, to generating whole set of tests based on an external configuration file.
-ForEach
& -TestCases
with hashtable#
Using The most common usage of data driven tests is providing multiple examples to a single It
. This way we reuse the body of the test. And decorate the test with examples of its usage. This is superior to copy-pasting tests for each example, because the amount of code to maintain is reduced, the amount of typos is reduced, and it is very easy to add more examples.
To define examples for a test, use -ForEach
on an It
block. On It
block the parameter is also aliased -TestCases
for backwards compatibility.
The data are provided as an array of hashtables:
@()
is an array. One test will be generated for each item in the array.
Inside of the @()
array, a hashtable is provided @{}
. This hashtable can have multiple keys, here it is Name
and Expected
, and each one of those keys will be defined as a variable in the test. These variables can be used in the test, and also in the <>
templates, described below.
The code above will generate tests that are equivalent to this:
Using -ForEach
(or -TestCases
) we can now very easily provide more examples just by adding more data:
-ForEach
on Describe
and Context
#
The same -ForEach
syntax is used on Describe
(or Context
) to generate those blocks from the provided data:
Which is equivalent to writing:
#
Choosing a subset of dataThe provided data will be available in both Discovery
and Run
, which allows us to generate tests based on the current data. Here for example we generate tests in a Context
, based on the data provided to the Describe
:
-ForEach
& -TestCases
with an array#
Using -ForEach
is most powerful when used with an array of hashtables, but it can take any array. In that case it will define $_
variable, representing the current item. This approach can be used on Describe
, Context
and It
:
tip
-ForEach
defines the $_
variable even when used with an array of hashtables. In that case $_
will be set to the current hashtable.
<>
templates#
Using Test and block names can contain values surrounded by <>
in their name. Those will be considered a template, and will be expanded. Any variable available in scope can be expanded using this syntax, not just the data from -ForEach
:
Additionally, the expansion of the string is postponed after the setup for the current block or test. This enables you to expand variables defined in BeforeAll
, and BeforeEach
of the current block, even though the code is visually below the name of the block:
<_>
in template#
The current item in the provided array is represented by $_
and can be expanded by using <_>
template.
<>
template#
Using dot-navigation in The <>
templates allow to navigate through the provided object by using dot-notation. This is useful when the data are not just a flat structure:
#
EscapingBefore the template string is expanded every $
and `
in the text is escaped. This allows you to put literals, such as $null
in your test names, and still use the template to expand the value:
To avoid a <
from being considered a template use `<
to escape it. This allows you to put greater than and less than in your test names. Or to wrap values in <>
:
#
InternalsThe value in the string is escaped by adding `
before every $
and `
. Every template is prefixed by $
, and then the string is expanded:
#
Providing external data to tests.Tests.ps1
script is a PowerShell script as any other and you can use the param
block in it. This is very useful when reusing a single test file with multiple data sets. For example when writing a generic test suite that ensures that correct style is used in a given script file:
The external data are then provided to the test by -Data
parameter on New-PesterContainer
, in the same manner -ForEach
would be used on Describe
or It
:
#
Re-using the same test fileIn the scenario above we tested just a single file, but changes are we want to run those style tests for every file in the repository. This can be achieved by generating one data item for each file:
This will run the test file twice, once for each file. You could even grab all .ps1
files in the current Path and test them, just by using $files = Get-ChildItem -Filter '*.ps1' -Recurse
.
The -Data
parameter takes an array of hashtables. As an alternative to the code above, you also attach all the test data to a single container:
There is no advantage in using one over the other, choose the one that suits your needs better.
#
Re-using the same data setIn the example above we had a single test file, and multiple different data sets. We can also have the opposite. A single data set used by multiple test files:
#
WildcardsNew-PesterContainer
resolves the -Path
parameter the same way Invoke-Pester
does. You can use it to provide a wildcard path to run all files matching the pattern. For example to run all *Style tests against all files in the current directory:
BeforeDiscovery
#
In Pester v4 it was normal to place code directly on top of the script file, or directly into the body of Describe
and Context
. Pester v5 discourages this, because all code directly in script or in Describe / Context
body will run during Discovery
. This leads to unexpected results.
BUT in some cases, especially when generating tests, putting code directly in script body is okay. BeforeDiscovery
was introduced to show that the code is like this intentionally, and not by accident. Wrapping code into BeforeDiscovery
has no impact on it's execution. It should be used everywhere where code is intentionally placed outside of a Pester controlled leaf-block (see Discovery and Run). That is on top of files, or directly in body of Describe / Context
.
Here for example I generate one Describe
for each script file, instead of passing the list of files externally. I need this code to run during Discovery
to generate those blocks. BeforeDiscovery
is used there to show that this code placement is intentional:
#
Migrating from Pester v4There are two main things to take into account when Data driven tests in Pester v5: The execution is not top down like in V4 but done in two phases Discovery
and Run
. Variables from Discovery
are not available in Run
, unless you explicitly attach them to a Describe
, Context
or It
using Foreach
or TestCases
This has big impact the classic pattern of using foreach
to generate tests.
#
Execution is not top downPester v5 introduces a new two phase execution. In the first phase called Discovery
, it will run your whole test script from top to bottom. It will also run all ScriptBlocks that you provided to any Describe
, Context
and BeforeDiscovery
. It will collect the ScriptBlocks you provided to It
, BeforeAll
, BeforeEach
, AfterEach
and AfterAll
, but won't run them until later.
Phase | V4 | V5 Discovery | V5 Run |
---|---|---|---|
BeforeAll | โ๏ธ | โ | โ๏ธ |
BeforeEach | โ๏ธ | โ | โ๏ธ |
It | โ๏ธ | โ | โ๏ธ |
AfterEach | โ๏ธ | โ | โ๏ธ |
AfterAll | โ๏ธ | โ | โ๏ธ |
BeforeDiscovery | N/A | โ๏ธ | โ |
Describe | โ๏ธ | โ๏ธ | โ |
Context | โ๏ธ | โ๏ธ | โ |
Other | โ๏ธ | โ๏ธ | โ |
Other, is any script outside of It
, BeforeAll
, BeforeEach
, AfterEach
and AfterAll
. You see that this code behaves like the new BeforeDiscovery
block and thus for clarity on when script will be run its advised to use the BeforeDiscovery
.
In simple terms this means that the script below will run until the last line during Discovery
. But no test will be executed:
Notice that "We are done with discovery!" is placed at the end of the script, but is printed before "I am running!". This is because we postpone the execution of It
until the Run
phase.
Discovery
are not available in Run
#
Variables defined during Defining a variable directly in the body of the script, will make it available during Discovery
, but it won't be available during Run
. You can see that the test failed because $name
is not defined.
This is especially confusing because $name
was correctly expanded in the name of the test. But if you think about it, it is logical. The script is running till the end, so the string "My name is: $name"
is expanded during Discovery
where the $name
variable is available.
foreach
#
Moving away from Those two limitations mean that this pattern, is difficult to use:
The $file
variable won't be defined in the It
and the test will fail. You can work around this problem by specifying a single item -ForEach
to attach the value to the block. -ForEach
(and -TestCases
) will inspect the provided value, and if it is a hashtable, it will define a variable for each key in that hashtable.
This will take the value of $file
during Discovery
and attach it to that Describe
block, and then during Run
it will define a new variable $file
with the same value.
Next step is to move to -ForEach
functionality entirely:
There are three notable changes:
$files
are defined inside ofBeforeDiscovery
block, to show that this code is running inDiscovery
intentionally, and not by accident$files
is provided directly to-ForEach
. This will generate oneDescribe
for each item in$file
, and will define the current item as$_
which is available in bothDiscovery
andRun
.The names of tests are using the
<>
template to expand the variable$file
duringRun
. Any variable in scope can be used for that expansion, including the variables defined in theBeforeAll
that is contained directly inside of the currentDescribe
.
foreach
#
Another variation on One more common approach is to put common test setup is directly in the Describe
block to ensure that the file will be loaded only once:
This approach can still be easily converted to Pester v5: