Skip to main content

Source facts

Repository
dotnet/dotnet
Last source activity
July 25, 2026 at 02:06
Detected SKILL.md language
English
Stars
1,222
Forks
335

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
apex-migration
description
>-
# Migrating PowerShell E2E Tests to Apex Tests PowerShell E2E tests live in `test/EndToEnd/tests/`. Apex tests live in `test/NuGet.Tests.Apex/NuGet.Tests.Apex/NuGetEndToEndTests/`. The goal is to migrate PS tests to C# Apex tests that run the **exact same scenario**, then remove the PS test function. ## Workflow 1. **Read the PS test** — understand what it does: which project type, which PMC commands, what assertions. 2. **Decide: unit test or Apex test** — if it only tests cmdlet behavior (filtering, listing, prerelease logic) without needing VS project system integration, write a unit test. Otherwise use Apex. 3. **Pick the right file** — for Apex, match the scenario to an existing test class (see File Placement below). For unit tests, use the cmdlet-specific file in `Cmdlets/` (see Unit test file placement below). 4. **Translate** — use the mappings in this skill to convert each PS construct to its C# equivalent. 5. **Verify** — run `get_errors` or build the project to confirm it compiles cleanly. 6. **Remove the PS function** — delete the migrated function from the PS test file. 7. **If already covered** — if an existing Apex or unit test already covers the same scenario, just delete the PS test. No new test needed. 8. **Update this skill** — if you discovered new mappings, gotchas, or corrections, add them to the appropriate section of this file. ## File placement Choose the target file by **interaction surface**, not project type: | Interaction surface | Apex file | |---|---| | `Get-Package` command scenarios (`-ListAvailable`, `-Updates`, `-Filter`, path source variants, prerelease behaviors) | `GetPackageTestCase.cs` | | Other PMC commands (Install-Package, Update-Package, Uninstall-Package, Get-Project) | `NuGetConsoleTestCase.cs` | | NuGet UI / Package Manager dialog | `NuGetUITestCase.cs` | | IVsPackageInstaller / IVsServices API | `IVsServicesTestCase.cs` | | Sync/binding redirect scenarios | `SyncPackageTestCase.cs` | | Audit / vulnerability scenarios | `NuGetAuditTests.cs` | | .NET Core project-creation / restore / source-mapping | `NetCoreProjectTestCase.cs` | PMC tests for PackageReference projects still follow command-based placement. For `Get-Package` scenarios use `GetPackageTestCase.cs`; for other PMC commands use `NuGetConsoleTestCase.cs`. ## Template mapping | PowerShell function | Apex `ProjectTemplate` | Package management | Verified | |---|---|---|---| | `New-ConsoleApplication` | `ProjectTemplate.ConsoleApplication` | packages.config | ✅ | | `New-ClassLibrary` | `ProjectTemplate.ClassLibrary` | packages.config | ✅ | | `New-WebSite` | `ProjectTemplate.WebSiteEmpty` | packages.config | ✅ | | `New-WebApplication` | `ProjectTemplate.WebApplicationEmpty` | packages.config | ❌ | | `New-WPFApplication` | `ProjectTemplate.WPFApplication` | packages.config | ❌ | | `New-MvcApplication` | `ProjectTemplate.WebApplicationEmptyMvc` | packages.config | ❌ | | `New-FSharpLibrary` | `ProjectTemplate.FSharpLibrary` | PackageReference | ❌ | | `New-NetCoreConsoleApp` | `ProjectTemplate.NetCoreConsoleApp` | PackageReference | ✅ | | `New-NetStandardClassLib` | `ProjectTemplate.NetStandardClassLib` | PackageReference | ✅ | | PowerShell function | Apex equivalent | |---|---| | `New-SolutionFolder 'Name'` | `testContext.SolutionService.AddSolutionFolder("Name")` | The package management style determines which assertion methods to use — packages.config projects use `AssertPackageInPackagesConfig`, while PackageReference projects use `AssertPackageInAssetsFile`. > **Note:** This table covers the most common PS project factories. Some PS tests use specialized > factories like `New-ClassLibraryNET46`, `New-BuildIntegratedProj`, `New-UwpPackageRefClassLibrary`, > or `New-NetCoreConsoleMultipleTargetFrameworksApp`. These don't have a 1:1 `ProjectTemplate` enum > value — check the Apex `ProjectTemplate` enum and existing tests for the closest match, or create > a standard template and modify the csproj afterward (e.g., for multi-targeting). ## Command execution | Scenario | Apex API | |---|---| | Standard install with `-Version` | `nugetConsole.InstallPackageFromPMC(packageName, packageVersion)` | | Install with extra flags (`-Source`, `-WhatIf`, `-IgnoreDependencies`) | `nugetConsole.Execute($"Install-Package {packageName} -ProjectName {project.Name} -Source {source}")` | | Standard uninstall | `nugetConsole.UninstallPackageFromPMC(packageName)` | | Standard update with `-Version` | `nugetConsole.UpdatePackageFromPMC(packageName, packageVersion)` | | Update with `-Safe`, `-Reinstall`, etc. | `nugetConsole.Execute($"Update-Package {packageName} -Safe")` | | Any raw PMC command | `nugetConsole.Execute(command)` | **Key rule:** Both `InstallPackageFromPMC()` and `UpdatePackageFromPMC()` always inject `-Version`. If the original PS test does **not** use `-Version`, use `Execute()` with the raw command string instead — using the helper changes the semantics. **PowerShell session state is accessible.** `nugetConsole.Execute()` runs in a live PMC PowerShell session. It can execute **any** PowerShell command, not just NuGet commands. This means PS session state — global variables (`$global:InstallVar`), registered functions (`Test-Path function:\Get-World`), environment checks — can all be queried and asserted via `Execute()` + `IsMessageFoundInPMC()`. Do not skip tests just because they assert PS session state. **Project-level build operations:** | Scenario | Apex API | |---|---| | Clean (deletes obj/bin) | `testContext.Project.Clean()` | | Rebuild | `testContext.Project.Rebuild()` | | Cache file path | `CommonUtility.GetCacheFilePath(testContext.Project)` | | Wait for file to appear | `CommonUtility.WaitForFileExists(path)` | | Wait for file to disappear | `CommonUtility.WaitForFileNotExists(path)` | For single-project solutions, project-level Clean/Rebuild is equivalent to solution-level. ## Assertion mapping | PowerShell assertion | Apex equivalent | |---|---| | `Assert-Package $p PackageName Version` (packages.config) | `CommonUtility.AssertPackageInPackagesConfig(VisualStudio, testContext.Project, packageName, version, Logger)` | | `Assert-Package $p PackageName` (no version, packages.config) | `CommonUtility.AssertPackageInPackagesConfig(VisualStudio, testContext.Project, packageName, Logger)` | | `Assert-Package $p PackageName Version` (PackageReference) | `CommonUtility.AssertPackageInAssetsFile(VisualStudio, testContext.Project, packageName, version, Logger)` | | `Assert-Throws { ... } $expectedMessage` | `nugetConsole.IsMessageFoundInPMC(expectedMessage)` — PMC errors appear as text, not C# exceptions | | `Assert-Null (Get-ProjectPackage ...)` / not installed | `CommonUtility.AssertPackageNotInPackagesConfig(VisualStudio, testContext.Project, packageName, Logger)` | | `Assert-NoPackage $p PackageName Version` (PackageReference) | `CommonUtility.AssertPackageNotInAssetsFile(VisualStudio, testContext.Project, packageName, version, Logger)` | | `Assert-PackageReference $p PackageName Version` | `CommonUtility.AssertPackageReferenceExists(VisualStudio, testContext.Project, packageName, version, Logger)` | | `Assert-NoPackageReference $p PackageName` | `CommonUtility.AssertPackageReferenceDoesNotExist(VisualStudio, testContext.Project, packageName, Logger)` | ## Package sources | PowerShell source | Apex equivalent | |---|---| | `$context.RepositoryRoot` / `$context.RepositoryPath` | `testContext.PackageSource` — create packages with `CommonUtility.CreatePackageInSourceAsync()` | | No `-Source` (uses nuget.org) | Create a local package with `CommonUtility.CreatePackageInSourceAsync(testContext.PackageSource, ...)` — never depend on nuget.org | | Hardcoded invalid sources (`http://example.com`, `ftp://...`) | Use the same hardcoded strings directly | ### Creating test packages For simple packages: ```csharp await CommonUtility.CreatePackageInSourceAsync(testContext.PackageSource, packageName, packageVersion); ``` For packages with dependencies: ```csharp await CommonUtility.CreateDependenciesPackageInSourceAsync( testContext.PackageSource, packageName, packageVersion, dependencyName, dependencyVersion); ``` For .NET Framework-specific packages: ```csharp await CommonUtility.CreateNetFrameworkPackageInSourceAsync( testContext.PackageSource, packageName, packageVersion); ``` ## NuGet.Config manipulation PS tests that use `Get-VSComponentModel` + `ISettings` to modify NuGet config at runtime can be migrated by pre-configuring `SimpleTestPathContext` before passing it to `ApexTestContext`. **Via Settings API** (preferred): ```csharp using var simpleTestPathContext = new SimpleTestPathContext(); simpleTestPathContext.Settings.AddSource("PrivateRepo", privatePath); using var testContext = new ApexTestContext(VisualStudio, projectTemplate, Logger, simpleTestPathContext: simpleTestPathContext); ``` **Via raw config file** (for settings not covered by the API like `dependencyVersion` or `bindingRedirects`): ```csharp using var simpleTestPathContext = new SimpleTestPathContext(); File.WriteAllText(simpleTestPathContext.NuGetConfig, $@"<?xml version=""1.0"" encoding=""utf-8""?> <configuration> <config> <add key=""dependencyVersion"" value=""HighestPatch"" /> </config> <packageSources> <clear /> <add key=""source"" value=""{simpleTestPathContext.PackageSource}"" /> </packageSources> </configuration>"); using var testContext = new ApexTestContext(VisualStudio, projectTemplate, Logger, simpleTestPathContext: simpleTestPathContext); ``` ## Test structure patterns ### Error-path tests (no package creation needed) — synchronous ```csharp [TestMethod] [Timeout(DefaultTimeout)] public void DescriptiveTestName_Fails() { using var testContext = new ApexTestContext(VisualStudio, ProjectTemplate.ConsoleApplication, Logger); var packageName = "Rules"; var source = @"c:\temp\data"; var expectedMessage = $"Unable to find package '{packageName}' at source '{source}'. Source not found."; var nugetConsole = GetConsole(testContext.Project); nugetConsole.Execute($"Install-Package {packageName} -ProjectName {testContext.Project.Name} -Source {source}"); Assert.IsTrue( nugetConsole.IsMessageFoundInPMC(expectedMessage), $"Expected error message was not found in PMC output. Actual output: {nugetConsole.GetText()}"); } ``` ### Success-path tests (need package creation) — async ```csharp [TestMethod] [Timeout(DefaultTimeout)] public async Task DescriptiveTestNameAsync() { using var testContext = new ApexTestContext(VisualStudio, ProjectTemplate.ConsoleApplication, Logger); var packageName = "TestPackage"; var packageVersion = "1.0.0"; await CommonUtility.CreatePackageInSourceAsync(testContext.PackageSource, packageName, packageVersion); var nugetConsole = GetConsole(testContext.Project); nugetConsole.InstallPackageFromPMC(packageName, packageVersion); CommonUtility.AssertPackageInPackagesConfig(VisualStudio, testContext.Project, packageName, packageVersion, Logger); } ``` ### Data-driven tests (multiple project templates) When the same scenario applies to multiple project types, use `[DataTestMethod]` or `[DynamicData]`: ```csharp [DataTestMethod] [DynamicData(nameof(GetPackageReferenceTemplates), DynamicDataSourceType.Method)] [Timeout(DefaultTimeout)] public async Task InstallPackageAsync(ProjectTemplate projectTemplate) { using var simpleTestPathContext = new SimpleTestPathContext(); EnsurePackageReferenceFormat(simpleTestPathContext, projectTemplate); using var testContext = new ApexTestContext(VisualStudio, projectTemplate, Logger, simpleTestPathContext: simpleTestPathContext); // ... test body } // Yields both legacy and SDK-style PackageReference templates private static IEnumerable<object[]> GetPackageReferenceTemplates() { yield return new object[] { ProjectTemplate.ConsoleApplication }; yield return new object[] { ProjectTemplate.NetCoreConsoleApp }; } // Only legacy (ConsoleApplication) needs this; SDK projects ignore it private static void EnsurePackageReferenceFormat(SimpleTestPathContext context, ProjectTemplate template) { if (template == ProjectTemplate.ConsoleApplication) context.Settings.SetPackageFormatToPackageReference(); } ``` Use `[DynamicData]` over `[DataRow]` when the template set is reused across multiple tests in the same class. This ensures one test method covers both legacy and SDK-style PackageReference projects. ### Multi-targeted project tests To create a multi-targeted project, modify the csproj after project creation: ```csharp using var testContext = new ApexTestContext(VisualStudio, ProjectTemplate.NetCoreConsoleApp, Logger); // Modify csproj to multi-target via XDocument: // change <TargetFramework> to <TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks> ``` ## Style rules Follow the repo-wide coding guidelines first — they apply to all code, not just migrations: [`docs/coding-guidelines.md`](../../../docs/coding-guidelines.md) (e.g. no `#region` blocks, no reflection, `var` usage, nullable enabled). Don't restate or duplicate those rules here; this section only lists conventions **specific to test migration** that supplement the common guidelines: - Use `using var` (inline using declaration), not `using (var ...) { }`. - Place migrated tests before the static helper methods (`GetNetCoreTemplates`, etc.) in the file. - Method names: `{Action}FromPMC{Scenario}[_Fails|Async]`. Suffix with `_Fails` for error tests, `Async` for async tests. - Always include `[Timeout(DefaultTimeout)]`. - Always include `nugetConsole.GetText()` in assertion failure messages for diagnostics. - The test class inherits `SharedVisualStudioHostTestClass` which provides `VisualStudio` and `Logger`. - Get PMC console via `GetConsole(testContext.Project)` helper method in the test class. - Don't use the method-delegates-to-async-helper pattern unless the helper is actually called from multiple classes. Inline the test logic directly in the test method. - Do NOT add "Migrated from" comments in the test code. The PR description tracks the mapping — the code should stand on its own without referencing the old PS test. ## Tests that should NOT be migrated Skip PS tests that: - Use `Assert-BindingRedirect` — binding redirect tests are already `[SkipTest]` in PS and not worth migrating. - Depend on **DTE project hierarchy semantics** (e.g., `Get-ProjectItem` to check tree structure, parent/child relationships). However, if the PS test only uses `Get-ProjectItem` / `Get-ProjectItemPath` to verify a **file exists on disk**, migrate it using filesystem assertions instead: `File.Exists(path)`, XML reads on the project file, or `CommonUtility.WaitForFileExists()`. ## After migration checklist 1. ✅ Remove the migrated function from the PS test file. 2. ✅ If a PS test is already covered by an existing Apex test (duplicate), just delete the PS test — no new Apex test needed. 3. ✅ Build the Apex project or run `get_errors` to verify it compiles cleanly. 4. ✅ Verify assertion methods match the project's package management style (packages.config vs PackageReference). ## Common gotchas - **Console width**: PMC output assertions are text-sensitive. The Apex infrastructure forces console width to 1024 to avoid wrapping issues. - **Restore timing**: After install/update operations, the Apex infrastructure handles waiting for restore completion. You generally don't need explicit waits. - **nuget.org dependency**: PS tests that don't specify `-Source` implicitly use nuget.org. Always replace this with local package creation via `CreatePackageInSourceAsync` — tests must not depend on external feeds. - **`NuGetApexTestService` limitations**: It does NOT expose `ISolutionManager` or VS DTE project
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub