The Real Dependency and the Real Execution Identity
📦 Run it yourself — the companion repository reserves
companion/tests/Integration/for tests that use real adapters and controlled test resources.
The phrase “works on my machine” is usually treated as a joke.
For PowerShell automation, it is also a precise diagnosis.
Your machine may have:
- Your interactive token
- Your profile
- Your cached credentials
- Your current directory
- Your module versions
- Your SSH known-host file
- Your proxy settings
- Your mapped drives
- Your local certificate store
Production may have none of them.
A unit test with mocks cannot prove that the real dependency and the real execution identity can complete the work.
What you will learn
- When to move from mocked tests to integration tests
- Why execution identity is part of the test environment
- How to test noninteractive PowerShell sessions
- How to separate dependency failure from code failure
- How to create controlled integration resources
- How to keep integration tests from becoming flaky gate noise
What a mock actually proved
In the fast gate, we might use:
Mock Get-PfgOperationState { 'NotStarted' }That proves how our code behaves when the state adapter returns NotStarted.
It does not prove:
- The adapter can authenticate
- The account can read the state store
- The provider returns the exact string
NotStarted - The provider handles a missing record as expected
- The installed module exposes the parameter names we used
- The network permits the connection
Those are integration claims.
The production identity is a dependency
Teams often write “test against the real API” and forget the account that will call it.
The same endpoint may behave differently for:
- A developer with administrator access
- A CI workload identity
- A scheduled-task account
- A group-managed service account
- A remoting endpoint
- A container identity
- A noninteractive SSH session
The identity controls more than authorization. It also changes the environment PowerShell sees.
The SFTP intake example
The Partner Feed Guardian receives partner files through SFTP. The developer tests successfully from a console:
Get-SftpPartnerBatch -PartnerId NORTHWINDThe scheduled job fails.
Possible causes include:
- The service account cannot read the private key
- The known-host entry exists only in the developer profile
- The module is installed in the developer’s user module path
- The working directory is different
- The service account cannot write to quarantine
- The job runs in Windows PowerShell while development used PowerShell 7
- The proxy or environment variables differ
None of these are bugs in the eligibility function. They are production integration failures.
Create an environment fingerprint
Before testing the remote operation, capture the environment that matters:
function Get-PfgExecutionFingerprint {
[CmdletBinding()]
param()
[pscustomobject]@{
UserName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
Edition = $PSVersionTable.PSEdition
ProcessBitness = if ([Environment]::Is64BitProcess) { '64-bit' } else { '32-bit' }
CurrentDirectory = (Get-Location).Path
ModulePath = $env:PSModulePath -split [IO.Path]::PathSeparator
NonInteractive = -not [Environment]::UserInteractive
}
}On non-Windows systems, use a cross-platform identity method instead of WindowsIdentity. The point is not the exact implementation. The point is to record the environment under which the evidence was produced.
A test can assert the approved identity and PowerShell version:
It 'runs under the approved intake identity' {
$fingerprint = Get-PfgExecutionFingerprint
$fingerprint.UserName | Should-BeString 'CONTOSO\svc-partner-intake'
$fingerprint.PowerShellVersion |
Should-MatchString '^7\.(4|5|6)\.'
}Be cautious with overly exact environment checks. Pin what the operation requires, not every incidental detail.
Test the real module path
A common production failure is loading the wrong module version.
It 'loads the approved SFTP adapter version' {
$module = Get-Module -ListAvailable PartnerSftpAdapter |
Sort-Object Version -Descending |
Select-Object -First 1
$module | Should-NotBeNull
$module.Version.ToString() | Should-BeString '3.2.1'
}This is not a generic “package exists” infrastructure example. The test is tied to a specific compatibility requirement for the production intake adapter.
Better still, run a real adapter operation and record the loaded module version in the test result or execution evidence.
Test access without processing a real batch
Integration tests should use a dedicated test partner or isolated folder.
Describe 'Partner SFTP integration' -Tag 'Layer.Integration', 'Environment.Lab' {
BeforeAll {
$partnerId = 'PFG-INTEGRATION'
$testBatchId = 'INTEGRATION-{0}' -f ([guid]::NewGuid().Guid)
}
It 'authenticates and lists the isolated integration folder' {
$items = Get-SftpPartnerItem -PartnerId $partnerId -Path '/integration'
$items | Should-NotBeNull
}
}A good test resource is:
- Dedicated to testing
- Identifiable
- Least-privileged
- Safe to clean up
- Excluded from business processing
- Rebuildable
Do not point the integration test at the normal inbound directory and hope naming conventions are enough.
Test the provider’s real error shape
Your unit test may simulate:
throw 'Access denied'The real adapter may throw a nested exception, write a nonterminating error, return a status object, or emit a native exit code.
An integration test should establish the behavior your production wrapper actually sees.
It 'returns an actionable authentication failure for an invalid integration key' {
{
Get-SftpPartnerItem `
-PartnerId 'PFG-INTEGRATION-INVALID' `
-Path '/integration' `
-ErrorAction Stop
} | Should-Throw -ExceptionType ([System.UnauthorizedAccessException])
}Use a deliberately invalid test identity or endpoint designed for this purpose. Do not repeatedly lock a real service account to test failure handling.
Run the same command noninteractively
A manual console is not the production host.
Run the integration suite through the actual mechanism whenever possible:
- Scheduled task
- CI runner
- Automation worker
- Remote endpoint
- Container entry point
- Service wrapper
For a scheduled task, create a test task using the same account and PowerShell executable but a controlled command:
pwsh.exe -NoLogo -NoProfile -NonInteractive `
-File C:\PfgTests\Invoke-IntegrationGate.ps1-NoProfile is especially valuable. If the production automation requires profile initialization, make that dependency explicit rather than letting it arrive accidentally from a developer profile.
Separate dependency unavailability from assertion failure
When a test cannot reach its lab dependency, the result needs a clear classification.
Do not turn this:
SFTP lab unavailable
into this:
Partner eligibility logic failed
They require different owners and different responses.
A setup block can stop the integration container with an actionable error:
BeforeAll {
if (-not (Test-PfgIntegrationEnvironment)) {
throw 'PFG integration environment is unavailable. Owner: Partner Platform.'
}
}For a required release gate, an unavailable dependency should stop the gate. Silently skipping the suite and calling the release green is not acceptable.
For an optional developer run, you may provide a separate configuration that excludes integration tests entirely.
Keep integration tests narrow
A real dependency does not justify testing everything through it.
Use integration tests to prove the boundary:
- Authentication
- Authorization
- Command compatibility
- Serialization
- Provider error behavior
- Read/write behavior on isolated resources
- Cleanup and retry semantics
Continue testing the full decision table in the fast gate. It is faster and easier to diagnose there.
Make test data disposable, not mysterious
Every created integration object should have:
- A test prefix
- A correlation ID
- A creation timestamp
- An owner
- A cleanup rule
Example batch ID:
$batchId = 'PFG-INT-{0:yyyyMMddHHmmss}-{1}' -f `
(Get-Date).ToUniversalTime(), `
([guid]::NewGuid().Guid.Substring(0, 8))This makes abandoned resources discoverable after a failed teardown.
Teardown is helpful, not infallible
Use AfterAll to clean controlled resources:
AfterAll {
Remove-PfgIntegrationBatch -BatchId $batchId -ErrorAction Continue
}But also provide an independent cleanup command:
Remove-PfgExpiredTestBatch -Prefix 'PFG-INT-' -OlderThan (New-TimeSpan -Hours 4)The PowerShell host can crash before AfterAll runs. Test safety cannot depend on graceful completion.
Integration test policy
A practical integration profile should document:
| Field | Example |
|---|---|
| Environment | Dedicated PFG integration endpoint |
| Identity | svc-pfg-integration |
| State changes | Isolated test folder only |
| Frequency | Before release and nightly |
| Timeout | Five minutes |
| Failure action | Block release; route environment failures to platform owner |
| Cleanup | AfterAll plus scheduled stale-resource cleanup |
What these tests prove
They can prove that the actual execution identity, installed adapter, network path, authentication method, and controlled provider resource work together as expected.
What they do not prove
They do not prove that:
- Every production partner is configured correctly
- Production routing matches source control
- The deployed module is the tested build
- A full production batch can be published safely
- The provider will never throttle or fail
Those claims require deployment and production validation.
Try it yourself
Choose one external dependency and answer:
- Which identity uses it in production?
- Which PowerShell executable and version run the command?
- Which module path is visible?
- Where are credentials or keys read?
- What isolated test resource can be used?
- What real failure shape does the provider produce?
- How are abandoned test resources found and removed?
Then create one integration test that runs under the production-like identity rather than your own console.
Common mistakes
Running integration tests only as the developer. The production identity is part of the dependency chain.
Using normal inbound folders as test space. Create isolated test resources and business-processing exclusions.
Skipping a required suite when the lab is unavailable. Unavailable evidence is not passing evidence.
Testing the full decision matrix through a slow provider. Keep logic in the fast gate; prove the boundary here.
Relying entirely on
AfterAllcleanup. Provide independent discovery and removal of stale test state.
Recap
Mocks prove your code’s behavior against controlled scenarios. Integration tests prove that the real dependency and real execution identity can work together.
For production PowerShell, identity, module path, noninteractive behavior, credentials, and provider error shape are not background details. They are testable dependencies.
Next up: Part 5 — State Changes, Partial Failure, and Safe Repetition. We will test what happens when publication succeeds, acknowledgement fails, and the automation must run again without duplicating production data.