-
Notifications
You must be signed in to change notification settings - Fork 241
Description
This is a tracking issue to collect the improvements to the CRUD razor scaffolder.
- Support persistent component state on the scaffolder
- On Edit.tt
- Support adding
NotFound
- ReplaceNavigateTo("notfound") with NavigationManager.NotFound() Blazor CRUD.
- Add NotFound.razor page to the project like:
@page "/not-found" @layout MainLayout <h3>Not Found</h3> <p>Sorry, the content you are looking for does not exist.</p>
- Add NotFoundPage parameter to the Router (typically on the
Routes
view.<Router ... NotFoundPage="typeof(Pages.NotFound)"> ... </Router>
- Add re-execution middleware with the route to the NotFound.razor page in Program.cs:
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
- Fix the scaffolder pattern instances that uses initializers to use null coalescing assignment during OnInitialized.
-
Scaffolding/src/dotnet-scaffolding/dotnet-scaffold-aspnet/Templates/BlazorCrud/Create.tt
Lines 69 to 70 in 495ae1c
[SupplyParameterFromForm] private <#= modelName #> <#= modelName #> { get; set; } = new();
-
The generator model lives in https://github.com/dotnet/Scaffolding/tree/main/src/Scaffolding/VS.Web.CG.Mvc/Blazor
the templates live in https://github.com/dotnet/Scaffolding/tree/main/src/Scaffolding/VS.Web.CG.Mvc/Templates/Blazor
Also, take a look at https://github.com/dotnet/Scaffolding/tree/main/src/dotnet-scaffolding/dotnet-scaffold-aspnet/Templates/BlazorCrud and https://github.com/dotnet/Scaffolding/tree/main/src/dotnet-scaffolding/dotnet-scaffold-aspnet/Models for places to update.
Update the scaffolder to implement the changes mentioned above and update or create new tests for it. This testing guide should help you
# Testing Guide for Scaffolders in the dotnet/Scaffolding Repository
## Overview
The scaffolding tests in this repository focus on validating that the code generation templates produce the expected output. The tests are primarily template-based integration tests that verify the generated code matches expected patterns and functionality.
## Key Testing Components
### Main Test Classes
The primary test classes for scaffolders are located in the test projects:
1. **`BlazorCrudScaffolderTests`** - Tests for Blazor CRUD scaffolding functionality
2. **`MinimalApiScaffolderTests`** - Tests for Minimal API scaffolding
3. **`MvcScaffolderTests`** - Tests for MVC scaffolding
4. **`IdentityScaffolderTests`** - Tests for Identity scaffolding
### Testing Approach
The tests in this repository follow a pattern of:
1. Setting up a scaffolding model with specific configuration
2. Executing the scaffolder to generate files
3. Verifying the generated content matches expected output
## Example: Testing Changes to Blazor CRUD Scaffolder
Let's walk through testing a change to the Create.tt template where we want to move the model initialization from a property initializer to the `OnInitialized` method.
### Current Template Code
```csharp
[SupplyParameterFromForm]
private <#= modelName #> <#= modelName #> { get; set; } = new();
Proposed Change
[SupplyParameterFromForm]
private <#= modelName #> <#= modelName #> { get; set; }
protected override void OnInitialized()
{
<#= modelName #> ??= new();
}
Test Examples
Example 1: Updating an Existing Test
When modifying the Create.tt template, you'll need to update the corresponding test in BlazorCrudScaffolderTests
:
// In BlazorCrudScaffolderTests.cs
[Fact]
public async Task BlazorCrudScaffolder_GeneratesCreatePage_WithOnInitialized()
{
// Arrange
var model = new BlazorCrudModel
{
ModelInfo = new ModelInfo
{
ModelTypeName = "Product",
ModelTypePluralName = "Products",
PrimaryKeyName = "Id",
ModelProperties = new List<PropertyInfo>
{
new PropertyInfo { Name = "Id", Type = "int" },
new PropertyInfo { Name = "Name", Type = "string" }
}
},
DbContextInfo = new DbContextInfo
{
DbContextClassName = "ApplicationDbContext",
EntitySetVariableName = "Products"
}
};
// Act
var scaffolder = new BlazorCrudScaffolder();
var generatedFiles = await scaffolder.GenerateAsync(model);
var createPageContent = generatedFiles.First(f => f.Name.Contains("Create.razor")).Content;
// Assert - Check for OnInitialized method presence
Assert.Contains("protected override void OnInitialized()", createPageContent);
Assert.Contains("Product ??= new()", createPageContent);
// Assert - Property should not have initializer
Assert.DoesNotContain("Product { get; set; } = new()", createPageContent);
}
Example 2: Creating a New Test for Null-Coalescing Behavior
Add a new test to verify the null-coalescing assignment behavior:
[Fact]
public async Task BlazorCrudScaffolder_Create_UsesNullCoalescingInOnInitialized()
{
// Arrange
var model = CreateTestBlazorCrudModel("Order", "Orders");
// Act
var scaffolder = new BlazorCrudScaffolder();
var result = await scaffolder.GenerateAsync(model);
var createPage = result.GetFileByName("Create.razor");
// Assert multiple scenarios
// 1. Verify OnInitialized method exists
Assert.Matches(@"protected\s+override\s+void\s+OnInitialized\(\)", createPage.Content);
// 2. Verify null-coalescing assignment pattern
Assert.Contains("Order ??= new()", createPage.Content);
// 3. Verify property declaration without initializer
var propertyPattern = @"\[SupplyParameterFromForm\]\s+private\s+Order\s+Order\s+{\s+get;\s+set;\s+}";
Assert.Matches(propertyPattern, createPage.Content);
}
Testing Template Changes
Step 1: Identify Affected Tests
For the Create.tt template change, look for tests in:
BlazorCrudScaffolderTests
that verify Create page generation- Any integration tests that validate the full CRUD scaffold output
Step 2: Update Expected Output
The tests often compare against expected string patterns or template outputs. You'll need to:
- Update any hardcoded expected strings that contain the old pattern
- Modify regex patterns that validate the generated code structure
- Update any baseline files if the tests use them for comparison
Step 3: Add Coverage for New Behavior
Create tests that specifically validate your new functionality:
[Theory]
[InlineData("Product", "Products")]
[InlineData("Category", "Categories")]
[InlineData("OrderItem", "OrderItems")]
public async Task BlazorCrudScaffolder_Create_InitializesModelInOnInitialized(string modelName, string pluralName)
{
// Arrange
var model = CreateTestBlazorCrudModel(modelName, pluralName);
// Act
var scaffolder = new BlazorCrudScaffolder();
var files = await scaffolder.GenerateAsync(model);
var createFile = files.First(f => f.Path.EndsWith("Create.razor"));
// Assert - Verify the OnInitialized method pattern
var expectedPattern = $@"protected override void OnInitialized\(\)\s*{{\s*{modelName} \?\?= new\(\);\s*}}";
Assert.Matches(expectedPattern, createFile.Content);
}
Test Organization Best Practices
- Group Related Tests: Keep tests for each template (Create, Edit, Delete, etc.) grouped together
- Use Descriptive Names: Test names should clearly indicate what template feature they're testing
- Test Edge Cases: Include tests for models with different property types, naming conventions, and namespaces
- Verify Template Logic: Test conditional template generation (e.g., required fields, different input types)
Running Tests
After making changes to templates:
- Run all BlazorCrudScaffolder tests to ensure no regressions
- Run integration tests that use the full scaffolding pipeline
- Verify generated code compiles by running build verification tests
Common Test Patterns
Verifying Generated Code Structure
// Test that the generated code has the expected structure
Assert.Contains("@page \"/products/create\"", generatedContent);
Assert.Contains("@inject IDbContextFactory<ApplicationDbContext> DbFactory", generatedContent);
Assert.Contains("<EditForm method=\"post\" Model=\"Product\"", generatedContent);
Testing Template Conditionals
// Test that required fields generate appropriate markup
var modelWithRequiredField = CreateModelWithRequiredProperty();
var result = await scaffolder.GenerateAsync(modelWithRequiredField);
Assert.Contains("aria-required=\"true\"", result.Content);
Assert.Contains("<span class=\"text-danger\">*</span>", result.Content);
Validating Namespace Handling
// Test that namespaces are correctly included
var modelWithNamespace = CreateModelWithCustomNamespace("MyApp.Models");
var result = await scaffolder.GenerateAsync(modelWithNamespace);
Assert.Contains("@using MyApp.Models", result.Content);
Helper Methods for Test Setup
Most test classes include helper methods to create test models:
private BlazorCrudModel CreateTestBlazorCrudModel(string modelName, string pluralName)
{
return new BlazorCrudModel
{
ModelInfo = new ModelInfo
{
ModelTypeName = modelName,
ModelTypePluralName = pluralName,
PrimaryKeyName = "Id",
ModelNamespace = "TestApp.Models",
ModelProperties = new List<PropertyInfo>
{
new PropertyInfo { Name = "Id", Type = "int" },
new PropertyInfo { Name = "Name", Type = "string", HasRequiredAttribute = true },
new PropertyInfo { Name = "Description", Type = "string" },
new PropertyInfo { Name = "Price", Type = "decimal" },
new PropertyInfo { Name = "CreatedDate", Type = "DateTime" }
}
},
DbContextInfo = new DbContextInfo
{
DbContextClassName = "ApplicationDbContext",
DbContextNamespace = "TestApp.Data",
EntitySetVariableName = pluralName
}
};
}
Testing Checklist
When making changes to scaffolding templates:
- Identify all templates affected by your change
- Review existing tests for those templates
- Update tests to match new expected output
- Add new tests for new functionality
- Test edge cases (empty models, special characters in names, etc.)
- Verify generated code compiles
- Test with different model configurations
- Ensure backward compatibility where appropriate
- Run all related test suites
- Update any documentation or comments in tests
Debugging Test Failures
When tests fail after template changes:
- Compare Generated vs Expected: Use test output to see the actual generated content
- Check Whitespace: Template changes often affect whitespace/formatting
- Verify Regex Patterns: Ensure regex patterns in tests account for variations in generated code
- Review Template Logic: Check if conditional logic in templates is being tested properly
- Use Debugger: Set breakpoints in test code to inspect generated content
Integration with CI/CD
The tests run automatically in the CI pipeline. Ensure:
- All tests pass locally before pushing
- New tests are included in the appropriate test projects
- Test names follow the existing naming conventions
- Tests are properly categorized (unit, integration, etc.)
This testing approach ensures that template changes are thoroughly validated and that the generated code meets the expected requirements for all scaffolding operations.