tasks-test-generation

Skill Variant: Use this skill for autonomous test generation with structured templates. For interactive test writing with user feedback, use test-generation instead.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "tasks-test-generation" with this command: npx skills add congdon1207/agents.md/congdon1207-agents-md-tasks-test-generation

Skill Variant: Use this skill for autonomous test generation with structured templates. For interactive test writing with user feedback, use test-generation instead.

Test Generation Workflow

When to Use This Skill

  • Creating unit tests for new code

  • Adding tests for bug fixes

  • Integration test development

  • Test coverage improvement

Pre-Flight Checklist

  • Identify code to test (command, query, entity, component)

  • Find existing test patterns: grep "Test.{Feature}" --include=".cs"

  • Determine test type (unit, integration, e2e)

  • Identify dependencies to mock

File Locations

Backend Tests

tests/ └── {Service}.Tests/ ├── UnitTests/ │ ├── Commands/ │ │ └── Save{Entity}CommandTests.cs │ ├── Queries/ │ │ └── Get{Entity}ListQueryTests.cs │ └── Entities/ │ └── {Entity}Tests.cs └── IntegrationTests/ └── {Feature}IntegrationTests.cs

Frontend Tests

src/PlatformExampleAppWeb/apps/{app}/src/app/ └── features/ └── {feature}/ ├── {feature}.component.spec.ts └── {feature}.store.spec.ts

Pattern 1: Command Handler Unit Test

public class SaveEmployeeCommandTests { private readonly Mock<IPlatformQueryableRootRepository<Employee>> _employeeRepoMock; private readonly Mock<IPlatformApplicationRequestContextAccessor> _contextMock; private readonly SaveEmployeeCommandHandler _handler;

public SaveEmployeeCommandTests()
{
    _employeeRepoMock = new Mock&#x3C;IPlatformQueryableRootRepository&#x3C;Employee>>();
    _contextMock = new Mock&#x3C;IPlatformApplicationRequestContextAccessor>();

    // Setup default context
    var requestContext = new Mock&#x3C;IPlatformApplicationRequestContext>();
    requestContext.Setup(x => x.UserId()).Returns("test-user-id");
    requestContext.Setup(x => x.CurrentCompanyId()).Returns("test-company-id");
    _contextMock.Setup(x => x.Current).Returns(requestContext.Object);

    _handler = new SaveEmployeeCommandHandler(
        Mock.Of&#x3C;ILoggerFactory>(),
        Mock.Of&#x3C;IPlatformUnitOfWorkManager>(),
        Mock.Of&#x3C;IServiceProvider>(),
        Mock.Of&#x3C;IPlatformRootServiceProvider>(),
        _employeeRepoMock.Object
    );
}

[Fact]
public async Task HandleAsync_CreateEmployee_ReturnsNewEmployee()
{
    // Arrange
    var command = new SaveEmployeeCommand
    {
        Name = "John Doe",
        Email = "john@example.com"
    };

    _employeeRepoMock
        .Setup(x => x.CreateAsync(It.IsAny&#x3C;Employee>(), It.IsAny&#x3C;CancellationToken>()))
        .ReturnsAsync((Employee e, CancellationToken _) => e);

    // Act
    var result = await _handler.HandleAsync(command, CancellationToken.None);

    // Assert
    Assert.NotNull(result.Employee);
    Assert.Equal("John Doe", result.Employee.Name);
    _employeeRepoMock.Verify(x => x.CreateAsync(
        It.Is&#x3C;Employee>(e => e.Name == "John Doe"),
        It.IsAny&#x3C;CancellationToken>()), Times.Once);
}

[Fact]
public async Task HandleAsync_UpdateEmployee_UpdatesExisting()
{
    // Arrange
    var existingEmployee = new Employee { Id = "emp-1", Name = "Old Name" };
    var command = new SaveEmployeeCommand
    {
        Id = "emp-1",
        Name = "New Name"
    };

    _employeeRepoMock
        .Setup(x => x.GetByIdAsync("emp-1", It.IsAny&#x3C;CancellationToken>()))
        .ReturnsAsync(existingEmployee);

    _employeeRepoMock
        .Setup(x => x.UpdateAsync(It.IsAny&#x3C;Employee>(), It.IsAny&#x3C;CancellationToken>()))
        .ReturnsAsync((Employee e, CancellationToken _) => e);

    // Act
    var result = await _handler.HandleAsync(command, CancellationToken.None);

    // Assert
    Assert.Equal("New Name", result.Employee.Name);
    _employeeRepoMock.Verify(x => x.UpdateAsync(
        It.Is&#x3C;Employee>(e => e.Name == "New Name"),
        It.IsAny&#x3C;CancellationToken>()), Times.Once);
}

[Fact]
public async Task HandleAsync_InvalidCommand_ReturnsValidationError()
{
    // Arrange
    var command = new SaveEmployeeCommand
    {
        Name = ""  // Invalid: empty name
    };

    // Act &#x26; Assert
    var result = command.Validate();
    Assert.False(result.IsValid);
    Assert.Contains(result.Errors, e => e.Contains("Name"));
}

}

Pattern 2: Query Handler Unit Test

public class GetEmployeeListQueryTests { private readonly Mock<IPlatformQueryableRootRepository<Employee>> _repoMock; private readonly GetEmployeeListQueryHandler _handler;

[Fact]
public async Task HandleAsync_WithFilters_ReturnsFilteredResults()
{
    // Arrange
    var employees = new List&#x3C;Employee>
    {
        new() { Id = "1", Name = "Active", Status = EmployeeStatus.Active },
        new() { Id = "2", Name = "Inactive", Status = EmployeeStatus.Inactive }
    };

    _repoMock.Setup(x => x.CountAsync(It.IsAny&#x3C;Expression&#x3C;Func&#x3C;Employee, bool>>>(), It.IsAny&#x3C;CancellationToken>()))
        .ReturnsAsync(1);

    _repoMock.Setup(x => x.GetAllAsync(It.IsAny&#x3C;Func&#x3C;IPlatformUnitOfWork, IQueryable&#x3C;Employee>, IQueryable&#x3C;Employee>>>(), It.IsAny&#x3C;CancellationToken>(), It.IsAny&#x3C;Expression&#x3C;Func&#x3C;Employee, object>>[]>()))
        .ReturnsAsync(employees.Where(e => e.Status == EmployeeStatus.Active).ToList());

    var query = new GetEmployeeListQuery
    {
        Statuses = [EmployeeStatus.Active],
        SkipCount = 0,
        MaxResultCount = 10
    };

    // Act
    var result = await _handler.HandleAsync(query, CancellationToken.None);

    // Assert
    Assert.Single(result.Items);
    Assert.Equal("Active", result.Items[0].Name);
}

}

Pattern 3: Entity Validation Test

public class EmployeeEntityTests { [Fact] public void UniqueExpr_ReturnsCorrectExpression() { // Arrange var employees = new List<Employee> { new() { CompanyId = "c1", UserId = "u1" }, new() { CompanyId = "c1", UserId = "u2" }, new() { CompanyId = "c2", UserId = "u1" } }.AsQueryable();

    // Act
    var expr = Employee.UniqueExpr("c1", "u1");
    var result = employees.Where(expr).ToList();

    // Assert
    Assert.Single(result);
    Assert.Equal("u1", result[0].UserId);
}

[Fact]
public async Task ValidateAsync_DuplicateCode_ReturnsError()
{
    // Arrange
    var repoMock = new Mock&#x3C;IPlatformQueryableRootRepository&#x3C;Employee>>();
    repoMock.Setup(x => x.AnyAsync(It.IsAny&#x3C;Expression&#x3C;Func&#x3C;Employee, bool>>>(), It.IsAny&#x3C;CancellationToken>()))
        .ReturnsAsync(true);  // Duplicate exists

    var employee = new Employee { Id = "new", Code = "EMP001", CompanyId = "c1" };

    // Act
    var result = await employee.ValidateAsync(repoMock.Object, CancellationToken.None);

    // Assert
    Assert.False(result.IsValid);
    Assert.Contains(result.Errors, e => e.Contains("already exists"));
}

[Fact]
public void ComputedProperty_IsActive_CalculatesCorrectly()
{
    // Arrange
    var activeEmployee = new Employee { Status = EmployeeStatus.Active, IsDeleted = false };
    var inactiveEmployee = new Employee { Status = EmployeeStatus.Inactive, IsDeleted = false };
    var deletedEmployee = new Employee { Status = EmployeeStatus.Active, IsDeleted = true };

    // Assert
    Assert.True(activeEmployee.IsActive);
    Assert.False(inactiveEmployee.IsActive);
    Assert.False(deletedEmployee.IsActive);
}

}

Pattern 4: Angular Component Test

describe('FeatureListComponent', () => { let component: FeatureListComponent; let fixture: ComponentFixture<FeatureListComponent>; let store: FeatureListStore; let apiMock: jasmine.SpyObj<FeatureApiService>;

beforeEach(async () => {
    apiMock = jasmine.createSpyObj('FeatureApiService', ['getList', 'delete']);

    await TestBed.configureTestingModule({
        imports: [FeatureListComponent],
        providers: [FeatureListStore, { provide: FeatureApiService, useValue: apiMock }]
    }).compileComponents();

    fixture = TestBed.createComponent(FeatureListComponent);
    component = fixture.componentInstance;
    store = TestBed.inject(FeatureListStore);
});

it('should create', () => {
    expect(component).toBeTruthy();
});

it('should load items on init', () => {
    // Arrange
    const items = [{ id: '1', name: 'Test' }];
    apiMock.getList.and.returnValue(of({ items, totalCount: 1 }));

    // Act
    fixture.detectChanges();

    // Assert
    expect(apiMock.getList).toHaveBeenCalled();
    expect(component.vm()?.items).toEqual(items);
});

it('should delete item', fakeAsync(() => {
    // Arrange
    store.updateState({ items: [{ id: '1', name: 'Test' }] });
    apiMock.delete.and.returnValue(of(void 0));

    // Act
    component.onDelete({ id: '1', name: 'Test' });
    tick();

    // Assert
    expect(apiMock.delete).toHaveBeenCalledWith('1');
    expect(component.vm()?.items.length).toBe(0);
}));

it('should show loading state', () => {
    // Arrange
    apiMock.getList.and.returnValue(new Subject()); // Never completes

    // Act
    fixture.detectChanges();

    // Assert
    expect(store.isLoading$('loadItems')()).toBe(true);
});

});

Pattern 5: Angular Store Test

describe('FeatureListStore', () => { let store: FeatureListStore; let apiMock: jasmine.SpyObj<FeatureApiService>;

beforeEach(() => {
    apiMock = jasmine.createSpyObj('FeatureApiService', ['getList', 'save', 'delete']);

    TestBed.configureTestingModule({
        providers: [FeatureListStore, { provide: FeatureApiService, useValue: apiMock }]
    });

    store = TestBed.inject(FeatureListStore);
});

it('should initialize with default state', () => {
    expect(store.currentVm().items).toEqual([]);
    expect(store.currentVm().pagination.pageIndex).toBe(0);
});

it('should load items', fakeAsync(() => {
    // Arrange
    const items = [{ id: '1', name: 'Test' }];
    apiMock.getList.and.returnValue(of({ items, totalCount: 1 }));

    // Act
    store.loadItems();
    tick();

    // Assert
    expect(store.currentVm().items).toEqual(items);
    expect(store.currentVm().pagination.totalCount).toBe(1);
}));

it('should update state immutably', () => {
    // Arrange
    const initialItems = store.currentVm().items;

    // Act
    store.updateState({ items: [{ id: '1', name: 'New' }] });

    // Assert
    expect(store.currentVm().items).not.toBe(initialItems);
});

it('should handle API error', fakeAsync(() => {
    // Arrange
    apiMock.getList.and.returnValue(throwError(() => new Error('API Error')));

    // Act
    store.loadItems();
    tick();

    // Assert
    expect(store.getErrorMsg$('loadItems')()).toContain('Error');
}));

});

Test Naming Convention

[MethodName][Scenario][ExpectedBehavior]

Examples:

  • HandleAsync_ValidCommand_ReturnsSuccess
  • HandleAsync_InvalidId_ThrowsNotFound
  • UniqueExpr_MatchingValues_ReturnsTrue
  • LoadItems_ApiError_SetsErrorState

Anti-Patterns to AVOID

:x: Testing implementation, not behavior

// WRONG - testing internal method calls Assert.True(handler.WasValidateCalled);

// CORRECT - testing observable behavior Assert.Equal("Expected", result.Value);

:x: Not mocking dependencies

// WRONG - using real repository var handler = new Handler(new RealRepository());

// CORRECT - using mock var repoMock = new Mock<IRepository>(); var handler = new Handler(repoMock.Object);

:x: Missing edge cases

// WRONG - only happy path [Fact] public void Save_ValidData_Succeeds() { }

// CORRECT - include edge cases [Fact] public void Save_EmptyName_ReturnsError() { } [Fact] public void Save_DuplicateCode_ReturnsError() { } [Fact] public void Save_NullInput_ThrowsException() { }

Verification Checklist

  • Unit tests cover happy path

  • Edge cases and error conditions tested

  • Dependencies properly mocked

  • Test naming follows convention

  • Assertions are specific and meaningful

  • No test interdependencies

  • Tests are deterministic (no random, no time-dependent)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

documentation

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

ui-ux-pro-max

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

tdd-workflow

No summary provided by upstream source.

Repository SourceNeeds Review