Skip to main content

Creating the sample module

Before you can test a module, you need a module. This page builds the empty shell of Planetarium — no functions yet, just the structure that everything else hangs off. It is deliberately the same structure most real PowerShell modules use, because the testing problems in later pages come from that structure.

The layout

Create the folders from inside your pester-tutorial working folder:

New-Item -Path ./Planetarium/Public -ItemType Directory -Force
New-Item -Path ./Planetarium/Private -ItemType Directory -Force

That gives you the three folders that matter:

  • Planetarium/ - the module's root folder.
  • Planetarium/Public/ — functions your users call. These get exported.
  • Planetarium/Private/ — helpers that only the module itself uses, hidden from the users.

That split is the whole reason this module is interesting to test. Public functions are easy to reach from a test. Private ones, by design, are not — that is a page of its own later.

The loader

The .psm1 file is the module's root script, executed when you import the module. Ours does not define any functions itself; it finds the .ps1 files, one per function, and dot-sources them.

Planetarium/Planetarium.psm1
$public = @(Get-ChildItem -Path "$PSScriptRoot/Public/*.ps1" -Exclude *.Tests.ps1 -ErrorAction SilentlyContinue)
$private = @(Get-ChildItem -Path "$PSScriptRoot/Private/*.ps1" -Exclude *.Tests.ps1 -ErrorAction SilentlyContinue)

foreach ($file in @($public + $private)) {
. $file.FullName
}

Export-ModuleMember -Function @($public.BaseName)

Two details are worth pausing on.

Export-ModuleMember -Function @($public.BaseName) exports everything in Public/ and nothing in Private/. The folder a file sits in is what decides whether it is part of your public surface — there is no attribute or naming convention doing it.

-Exclude *.Tests.ps1 allows us to place the tests files next to the code. Without that exclusion the loader would dot-source your test files into the module — and then export them.

Test files next to your code need excluding from the loader

If you skip -Exclude *.Tests.ps1, your tests get loaded as part of the module. This tends to fail in confusing ways rather than obvious ones, because your Describe blocks run at import time. If you would rather keep tests entirely separate, a mirrored tests/ folder is the other convention Pester supports — see file placement and naming.

The manifest

The .psd1 manifest is the module's metadata. Generate it rather than writing it by hand:

New-ModuleManifest -Path ./Planetarium/Planetarium.psd1 `
-RootModule 'Planetarium.psm1' `
-ModuleVersion '0.1.0' `
-Author 'Your Name' `
-Description 'Explore the solar system.' `
-PowerShellVersion '5.1'

Open the generated file and find the FunctionsToExport line:

Planetarium/Planetarium.psd1
FunctionsToExport = '*'

This is a second gate in front of Export-ModuleMember. A function has to pass both to be visible to your users. Leaving it as '*' means the manifest waves everything through and the .psm1 makes the real decision, which is what we want while functions are still being added.

Update before publishing a module

Using FunctionsToExport = '*' is convenient while developing, but should be changed to an array of public function names before publishing. This is required for best performance and auto-import.

For this module: FunctionsToExport = @('Get-Planet','Get-PlanetDistance','Export-PlanetReport').

Checking it imports

Nothing is in the module yet, but we can still try to load it:

Import-Module ./Planetarium/Planetarium.psd1 -Force -PassThru
Export-ModuleMember: Cannot bind parameter 'Function' to the target. Exception setting "Function": "Cannot process argument because the value of argument "pattern" is null. Change the value of argument "pattern" to a non-null value."

ModuleType Version PreRelease Name
---------- ------- ---------- ----
Script 0.1.0 Planetarium

The error above is expected for now, as there are no function files in the Public folder yet.

Why -Force appears on every import in this tutorial

PowerShell will not re-import a module it already has loaded. Without -Force you would keep testing the version of the code you imported the first time, and edits would appear to have no effect. This will bite you at least once anyway; now you will know what it is.

Before you move on

0/5

The shell is up. Next you will write a test — before there is anything to test.