From test-generator
分析函数逻辑并生成合适的断言。 当用户说"生成断言"、"写断言"、"expect语句"、"assert断言"、"验证返回值"、"测试验证"、"断言代码"时使用此技能。 支持多框架断言生成:Jest (expect)、Vitest (expect)、Python (assert/pytest)。 生成类型断言、边界值验证、深度对象比较、Mock调用验证、副作用验证、异步断言。输出精确、可维护的断言代码。
How this skill is triggered — by the user, by Claude, or both
Slash command
/test-generator:assertion-helperThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
这个技能负责分析函数逻辑并生成合适的断言,确保测试能够准确验证代码的行为。
这个技能负责分析函数逻辑并生成合适的断言,确保测试能够准确验证代码的行为。
// 为函数生成断言
await generateAssertions('calculateTotal', {
inputs: [{ items: [{ price: 10, qty: 2 }] }],
framework: 'vitest'
});
// 生成类方法的断言
await generateAssertions('UserService.createUser', {
context: 'class',
framework: 'vitest'
});
await generateAssertions(target, {
framework: 'pytest',
assertionStyle: 'strict', // 'strict' | 'loose' | 'custom'
includeSideEffects: true,
verifyMocks: true,
customAssertions: {
'shouldBeValidUser': 'expect(user).toMatchObject({ id: expect.any(Number), name: expect.any(String) })'
}
});
// 基础类型断言
{{#if returns}}
{{#eq returns.type 'string'}}
expect(result).toBeString();
expect(result).toHaveLength({{#if returns.length}}{{returns.length}}{{else}}greaterThan(0){{/if}});
{{/eq}}
{{#eq returns.type 'number'}}
expect(result).toBeNumber();
expect(result).{{#if returns.range}}toBeGreaterThanOrEqual({{returns.range.min}});
expect(result).toBeLessThanOrEqual({{returns.range.max}});{{/if}}
{{/eq}}
{{#eq returns.type 'boolean'}}
expect(result).toBeBoolean();
{{/eq}}
{{#eq returns.type 'array'}}
expect(result).toBeArray();
expect(result).toHaveLength({{#if returns.length}}{{returns.length}}{{else}}greaterThan(0){{/if}});
{{#if returns.items}}
expect(result[0]).toMatchObject({{{json returns.items}}});
{{/if}}
{{/eq}}
{{#eq returns.type 'object'}}
expect(result).toBeObject();
{{#if returns.properties}}
expect(result).toMatchObject({
{{#each returns.properties}}
{{@key}}: {{#if this.type}}expect.any({{camelCase this.type}}){{else}}{{{json this}}}{{/if}},
{{/each}}
});
{{/if}}
{{/eq}}
{{/if}}
// 异步断言
{{#if isAsync}}
await expect(asyncFunction()).resolves.toBe(expectedValue);
await expect(asyncFunction()).resolves.not.toThrow();
// 错误断言
await expect(asyncFunction()).rejects.toThrow('Expected error message');
{{/if}}
// Mock验证断言
{{#each mocks}}
{{#if this.shouldBeCalled}}
expect({{name}}).toHaveBeenCalled();
expect({{name}}).toHaveBeenCalledTimes({{this.callCount}});
{{#if this.withParams}}
expect({{name}}).toHaveBeenCalledWith({{#each this.withParams}}{{this}}{{#unless @last}}, {{/unless}}{{/each}});
{{/if}}
{{else}}
expect({{name}}).not.toHaveBeenCalled();
{{/if}}
{{/each}}
// 副作用验证
{{#if sideEffects}}
{{#each sideEffects}}
{{#eq this.type 'console'}}
// Console输出验证
expect(console.{{this.method}}).toHaveBeenCalledWith({{{json this.args}}});
{{/eq}}
{{#eq this.type 'event'}}
// 事件触发验证
expect(eventEmitter.emit).toHaveBeenCalledWith('{{this.eventName}}', {{#if this.payload}}{{{json this.payload}}}{{else}}expect.any(Object){{/if}});
{{/eq}}
{{#eq this.type 'state'}}
// 状态变化验证
expect(component.state.{{this.property}}).toBe({{{json this.value}}});
{{/eq}}
{{/each}}
{{/if}}
import { expect } from 'vitest';
// 类型断言
{{#if returns}}
expect(result).toBeDefined();
{{#eq returns.type 'string'}}
expect(typeof result).toBe('string');
{{/eq}}
{{#eq returns.type 'number'}}
expect(typeof result).toBe('number');
{{/eq}}
{{#eq returns.type 'object'}}
expect(typeof result).toBe('object');
expect(result).not.toBeNull();
{{/eq}}
{{/if}}
// 精确匹配
expect(result).toEqual(expectedValue);
// 部分匹配
expect(result).toMatchObject(partialExpected);
// 数组断言
expect(result).toContain(expectedItem);
expect(result).toHaveLength(expectedLength);
// 异常断言
expect(() => functionThatThrows()).toThrow();
expect(() => functionThatThrows()).toThrowError('Error message');
# 基础断言
assert result is not None
{{#eq returns.type 'string'}}
assert isinstance(result, str)
assert len(result) > 0
{{/eq}}
{{#eq returns.type 'number'}}
assert isinstance(result, (int, float))
assert result >= {{#if returns.min}}{{returns.min}}{{else}}0{{/if}}
{{/eq}}
{{#eq returns.type 'list'}}
assert isinstance(result, list)
assert len(result) > 0
{{#if returns.items}}
assert result[0] == {{returns.items}}
{{/if}}
{{/eq}}
{{#eq returns.type 'dict'}}
assert isinstance(result, dict)
{{#if returns.keys}}
for key in {{json returns.keys}}:
assert key in result
{{/if}}
{{/eq}}
# 异常断言
with pytest.raises({{errorType}}, match="{{errorMessage}}"):
function_that_raises()
# Mock验证
{{#each mocks}}
{{#if this.shouldBeCalled}}
assert {{name}}.called
assert {{name}}.call_count == {{this.callCount}}
{{#if this.withArgs}}
{{name}}.assert_called_with({{#each this.withArgs}}{{this}}{{#unless @last}}, {{/unless}}{{/each}})
{{/if}}
{{else}}
assert not {{name}}.called
{{/if}}
{{/each}}
class AssertionGenerator {
static generateForReturn(returnType: TypeInfo, value?: any): string[] {
const assertions: string[] = [];
// 类型断言
switch (returnType.type) {
case 'string':
assertions.push('expect(typeof result).toBe("string")');
if (value) {
assertions.push(`expect(result).toBe("${value}")`);
}
break;
case 'number':
assertions.push('expect(typeof result).toBe("number")');
if (returnType.constraints?.min !== undefined) {
assertions.push(`expect(result).toBeGreaterThanOrEqual(${returnType.constraints.min})`);
}
if (returnType.constraints?.max !== undefined) {
assertions.push(`expect(result).toBeLessThanOrEqual(${returnType.constraints.max})`);
}
break;
case 'boolean':
assertions.push('expect(typeof result).toBe("boolean")');
break;
case 'array':
assertions.push('expect(Array.isArray(result)).toBe(true)');
if (value?.length) {
assertions.push(`expect(result).toHaveLength(${value.length})`);
}
break;
case 'object':
assertions.push('expect(typeof result).toBe("object")');
assertions.push('expect(result).not.toBeNull()');
if (value) {
assertions.push(`expect(result).toMatchObject(${JSON.stringify(value)})`);
}
break;
}
return assertions;
}
static generateBoundaryTests(
functionName: string,
params: ParameterInfo[]
): string[] {
const tests: string[] = [];
params.forEach(param => {
if (param.constraints?.min !== undefined) {
tests.push(`
it('should handle minimum value for ${param.name}', () => {
const result = ${functionName}(${param.constraints.min});
expect(result).toBeDefined();
});
`);
}
if (param.constraints?.max !== undefined) {
tests.push(`
it('should handle maximum value for ${param.name}', () => {
const result = ${functionName}(${param.constraints.max});
expect(result).toBeDefined();
});
`);
}
});
return tests;
}
}
// 错误场景断言生成
function generateErrorAssertions(functionInfo) {
const assertions = [];
// 可能的错误条件
if (functionInfo.params.some(p => p.required)) {
assertions.push(`
it('should throw error when required parameter is missing', () => {
expect(() => ${functionInfo.name}()).toThrow();
expect(() => ${functionInfo.name}(null)).toThrow();
});
`);
}
if (functionInfo.params.some(p => p.type === 'number')) {
assertions.push(`
it('should handle invalid number inputs', () => {
expect(() => ${functionInfo.name}(NaN)).toThrow();
expect(() => ${functionInfo.name}(Infinity)).toThrow();
});
`);
}
return assertions;
}
// 异步函数断言生成
function generateAsyncAssertions(functionInfo) {
return `
describe('async behavior', () => {
it('should resolve with expected value', async () => {
const result = await ${functionInfo.name}(${generateValidParams(functionInfo)});
expect(result).toBeDefined();
${generateReturnAssertions(functionInfo.returnType)}
});
it('should handle rejection', async () => {
// 测试拒绝情况
await expect(${functionInfo.name}(${generateErrorParams()}))
.rejects.toThrow();
});
it('should handle timeout', async () => {
// 如果有超时配置
await expect(${functionInfo.name}(${generateSlowParams()}))
.rejects.toThrow('timeout');
});
});
`;
}
// 深度匹配断言
function generateDeepObjectAssertions(obj: any, path: string = ''): string[] {
const assertions: string[] = [];
for (const [key, value] of Object.entries(obj)) {
const currentPath = path ? `${path}.${key}` : key;
if (typeof value === 'object' && value !== null) {
if (Array.isArray(value)) {
assertions.push(`
expect(result.${currentPath}).toBeArray();
expect(result.${currentPath}).toHaveLength(${value.length});
`);
} else {
assertions.push(`
expect(result.${currentPath}).toBeObject();
`);
// 递归处理嵌套对象
assertions.push(...generateDeepObjectAssertions(value, currentPath));
}
} else {
assertions.push(`
expect(result.${currentPath}).toBe(${JSON.stringify(value)});
`);
}
}
return assertions;
}
// Mock函数调用断言
function generateMockAssertions(mocks: MockInfo[]): string[] {
return mocks.map(mock => {
const assertions: string[] = [];
if (mock.expectCall) {
assertions.push(`expect(${mock.name}).toHaveBeenCalled();`);
if (mock.expectedCallCount) {
assertions.push(
`expect(${mock.name}).toHaveBeenCalledTimes(${mock.expectedCallCount});`
);
}
if (mock.expectedArgs) {
assertions.push(
`expect(${mock.name}).toHaveBeenCalledWith(${mock.expectedArgs.join(', ')});`
);
}
if (mock.expectedCalls) {
mock.expectedCalls.forEach((call, index) => {
assertions.push(
`expect(${mock.name}).toHaveBeenNthCalledWith(${index + 1}, ${call.join(', ')});`
);
});
}
} else {
assertions.push(`expect(${mock.name}).not.toHaveBeenCalled();`);
}
return assertions.join('\n');
});
}
// 副作用断言生成
function generateSideEffectAssertions(sideEffects: SideEffect[]): string[] {
return sideEffects.map(effect => {
switch (effect.type) {
case 'console':
return `expect(console.${effect.method}).toHaveBeenCalledWith(${JSON.stringify(effect.args)});`;
case 'state':
return `expect(${effect.target}).toBe(${JSON.stringify(effect.value)});`;
case 'event':
return `expect(${effect.emitter}.emit).toHaveBeenCalledWith('${effect.event}', ${JSON.stringify(effect.payload)});`;
case 'storage':
return `expect(localStorage.getItem('${effect.key}')).toBe('${effect.value}');`;
default:
return '';
}
}).filter(Boolean);
}
npx claudepluginhub protagonistss/ithinku-plugins --plugin test-generatorProvides Vitest testing patterns: assertions, mocking with vi.fn/vi.mock, async testing, and verification gates for writing and running tests.
Generates test files with happy path, edge case, and error path coverage that run and pass on first try. Detects project's test framework, analyzes target modules, and iterates up to 3 fix cycles before marking failures as skipped.
Generates unit tests for code or functions, analyzing inputs, paths, edges, and errors. Detects project test framework (pytest, Jest, etc.), applies mocks, matches style, and writes files after confirmation.