
Tasks Test Generation
- 24 installs
- 7 repo stars
- Updated June 18, 2026
- duc01226/easyplatform
Autonomously generates or enhances unit and integration tests and defines test strategies for backend and frontend code.
About
An autonomous subagent variant of test-generation that creates or enhances unit tests, integration tests, and test strategies. A developer uses it to build out test coverage for backend and frontend code without manual scaffolding.
- Subagent-driven test generation for backend and frontend
- Covers unit tests, integration tests, and test strategy
Tasks Test Generation by the numbers
- 24 all-time installs (skills.sh)
- Ranked #1,397 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/duc01226/easyplatform --skill tasks-test-generationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 24 |
|---|---|
| repo stars | ★ 7 |
| Last updated | June 18, 2026 |
| Repository | duc01226/easyplatform ↗ |
What it does
Autonomously generates or enhances unit and integration tests and defines test strategies for backend and frontend code.
Files
[IMPORTANT] Use TaskCreate to break ALL work into small tasks BEFORE starting — including tasks for each file read. This prevents context loss from long files. For simple tasks, AI MUST ask user whether to skip.Prerequisites: MUST READ .claude/skills/shared/evidence-based-reasoning-protocol.md before executing.
Quick Summary
Goal: Autonomously generate unit and integration tests for backend (C#) and frontend (Angular) code using structured patterns.
Workflow:
1. Pre-Flight — Identify code to test, find existing patterns, determine test type 2. Read Patterns — MUST READ references/test-patterns.md for 5 canonical patterns 3. Write Tests — Follow Arrange-Act-Assert, mock dependencies, cover happy + edge + error paths 4. Verify — Ensure naming convention, no interdependencies, deterministic results
Key Rules:
- MUST READ
references/test-patterns.mdbefore writing any test - Test behavior, not implementation details
- Follow naming:
[Method]_[Scenario]_[ExpectedBehavior]
Be skeptical. Apply critical thinking, sequential thinking. Every claim needs traced proof.
Skill Variant: Use this skill for autonomous test generation with structured templates. For interactive test writing with user feedback, use test-spec instead.Test Generation Workflow
Prerequisites
⚠️ MUST READ references/test-patterns.md before executing — contains 5 complete test patterns (Command Handler, Query Handler, Entity Validation, Angular Component, Angular Store) and anti-patterns with correct/incorrect examples required by the Test Patterns section.
When to Use
- Creating unit tests for new code
- Adding tests for bug fixes
- Integration test development
- Test coverage improvement
For real-infrastructure integration tests (no mocks, real DI + DB), use `integration-test` skill instead.
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.csFrontend Tests
{frontend-apps-dir}/{app}/src/app/features/{feature}/
{feature}.component.spec.ts
{feature}.store.spec.tsTest Patterns
| Pattern | Use Case | Reference |
|---|---|---|
| Command Handler | CQRS command create/update/delete | ⚠️ MUST READ: references/test-patterns.md Pattern 1 |
| Query Handler | CQRS query with filters/paging | ⚠️ MUST READ: references/test-patterns.md Pattern 2 |
| Entity Validation | UniqueExpr, ValidateAsync, computed props | ⚠️ MUST READ: references/test-patterns.md Pattern 3 |
| Angular Component | Component lifecycle, store interaction | ⚠️ MUST READ: references/test-patterns.md Pattern 4 |
| Angular Store | State management, API effects, error handling | ⚠️ MUST READ: references/test-patterns.md Pattern 5 |
Test Naming Convention
[MethodName]_[Scenario]_[ExpectedBehavior]
Examples:
- HandleAsync_ValidCommand_ReturnsSuccess
- HandleAsync_InvalidId_ThrowsNotFound
- UniqueExpr_MatchingValues_ReturnsTrue
- LoadItems_ApiError_SetsErrorStateKey Principles
- Test behavior, not implementation details
- Mock all dependencies (use
Mock<IRepository>,jasmine.createSpyObj) - Cover happy path + edge cases + error conditions
- Use Arrange-Act-Assert pattern consistently
- Anti-patterns and examples in
references/test-patterns.md
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)
Related
test-spec- Interactive test writingtasks-code-review- Code review with test coverage checks
References
| File | Contents |
|---|---|
references/test-patterns.md | 5 complete test patterns (Command Handler, Query Handler, Entity Validation, Angular Component, Angular Store), anti-patterns with correct/incorrect examples |
---
IMPORTANT Task Planning Notes (MUST FOLLOW)
- Always plan and break work into many small todo tasks
- Always add a final review todo task to verify work quality and identify fixes/enhancements
Test Generation Patterns & Examples
Pattern 1: Command Handler Unit Test
public class SaveEmployeeCommandTests
{
private readonly Mock<I{Service}RootRepository<Employee>> _employeeRepoMock;
private readonly Mock<IRequestContextAccessor // project request context accessor (search for actual name)> _contextMock;
private readonly SaveEmployeeCommandHandler _handler;
public SaveEmployeeCommandTests()
{
_employeeRepoMock = new Mock<I{Service}RootRepository<Employee>>();
_contextMock = new Mock<IRequestContextAccessor // project request context accessor (search for actual name)>();
// Setup default context
var requestContext = new Mock<IRequestContext // project request context (search for actual name)>();
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<ILoggerFactory>(),
Mock.Of<IUnitOfWorkManager // project UoW manager (search for actual name)>(),
Mock.Of<IServiceProvider>(),
Mock.Of<IRootServiceProvider // project root service provider (search for actual name)>(),
_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<Employee>(), It.IsAny<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<Employee>(e => e.Name == "John Doe"),
It.IsAny<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<CancellationToken>()))
.ReturnsAsync(existingEmployee);
_employeeRepoMock
.Setup(x => x.UpdateAsync(It.IsAny<Employee>(), It.IsAny<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<Employee>(e => e.Name == "New Name"),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task HandleAsync_InvalidCommand_ReturnsValidationError()
{
// Arrange
var command = new SaveEmployeeCommand
{
Name = "" // Invalid: empty name
};
// Act & 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<I{Service}RootRepository<Employee>> _repoMock;
private readonly GetEmployeeListQueryHandler _handler;
[Fact]
public async Task HandleAsync_WithFilters_ReturnsFilteredResults()
{
// Arrange
var employees = new List<Employee>
{
new() { Id = "1", Name = "Active", Status = EmployeeStatus.Active },
new() { Id = "2", Name = "Inactive", Status = EmployeeStatus.Inactive }
};
_repoMock.Setup(x => x.CountAsync(It.IsAny<Expression<Func<Employee, bool>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(1);
_repoMock.Setup(x => x.GetAllAsync(It.IsAny<Func<IUnitOfWork // project unit of work (search for actual name), IQueryable<Employee>, IQueryable<Employee>>>(), It.IsAny<CancellationToken>(), It.IsAny<Expression<Func<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<I{Service}RootRepository<Employee>>();
repoMock.Setup(x => x.AnyAsync(It.IsAny<Expression<Func<Employee, bool>>>(), It.IsAny<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');
}));
});---
Anti-Patterns to AVOID
Testing implementation, not behavior
// WRONG - testing internal method calls
Assert.True(handler.WasValidateCalled);
// CORRECT - testing observable behavior
Assert.Equal("Expected", result.Value);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);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() { }