Skip to content

Commit

Permalink
Merge pull request #6 from Testy/infer-test-and-test-suite-names-from…
Browse files Browse the repository at this point in the history
…-code

As a user, I want to be able to write @test() myTestName() and have the test runner infer the test name from the method name
  • Loading branch information
Aboisier authored Dec 15, 2018
2 parents d15bcac + 7a4fa81 commit b47b12f
Show file tree
Hide file tree
Showing 20 changed files with 192 additions and 89 deletions.
41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ $ testyts init
Writing tests with Testy is simple. Don't forget to export your test suites though. Otherwise, they won't be discovered by the test runner.

```ts
@testSuite('Sum Test Suite')
@testSuite()
export class MyTestSuite {

@test('One plus one, should equal two')
@test()
onePlusOne() {
// Act
const result = 1 + 1;
Expand All @@ -47,7 +47,7 @@ export class MyTestSuite {
Testy provides setup and teardown hooks.

```ts
@testSuite('Sum Test Suite')
@testSuite()
export class MyTestSuite {

@beforeAll()
Expand Down Expand Up @@ -81,7 +81,7 @@ class MyBaseTestSuite{
// Setup/teardown extravaganza
}

@testSuite('My Test Suite')
@testSuite()
class MyTestSuite extends MyBaseTestSuite {
// My tests
}
Expand All @@ -90,7 +90,7 @@ class MyTestSuite extends MyBaseTestSuite {
### Test cases

```ts
@testSuite('Sum Test Suite')
@testSuite()
export class MyTestSuite {
@test('Addition', [
new TestCase('Two plus two is four', 2, 2, 4),
Expand Down Expand Up @@ -120,14 +120,14 @@ expect.toBeSorted.inAscendingOrder([0, 1, 1, 2, 3, 5, 8]);
You can ignore tests by adding an `x` before a test suite or a specific test decorator. Ignored tests will still show up in the test report, but they will be marked as ignored.

```ts
@xtestSuite('Sum Test Suite') // This test suite will be ignored
@xtestSuite() // This test suite will be ignored
export class MyTestSuite {
// Your tests
}

@testSuite('Sum Test Suite')
@testSuite()
export class MyTestSuite {
@xtest('One plus one, should equal two') // This test will be ignored
@xtest() // This test will be ignored
onePlusOne() {
// Some test
}
Expand All @@ -137,20 +137,39 @@ export class MyTestSuite {
You can also focus tests by adding an `f` before a test suite or a specific test decorator. If one test or test suites are focused, only those will be runned. The others will be reported as ignored.

```ts
@ftestSuite('Sum Test Suite') // This test suite will be focused.
@ftestSuite() // This test suite will be focused.
export class MyTestSuite {
...
}

@testSuite('Sum Test Suite')
@testSuite()
export class MyTestSuite {
@ftest('One plus one, should equal two') // This test will be focused
@ftest() // This test will be focused
onePlusOne() {
// Your test
}
}
```

## Custom tests and test suites names

The tests and test suites names are inferred from the method or class name by default. You can specify a custom name.

```ts
@testSuite('My glorious test suite')
export class MyTestSuite {

@test('Adding one plus one, should equal two')
onePlusOne() {
// Act
const result = 1 + 1;

// Assert
expect.toBeEqual(result, 2);
}
}
```

## Run the tests

To run the tests, use the following command
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "testyts",
"version": "0.4.0",
"version": "0.4.1",
"icon": "img/test-icon-128x128.png",
"description": "Modern test framework for TypeScript.",
"main": "build/testyCore.js",
Expand Down
38 changes: 14 additions & 24 deletions src/lib/decorators/test.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,8 @@ import { TestSuite } from '../tests/testSuite';
* @param testCases Allows to run the test multiple times with different arguments. Arguments will be passed to the test class.
* @param timeout The test will automaticlaly fail if it has been running for longer than the specified timeout.
*/
export function test(name: string, testCases?: TestCase[], timeout: number = 2000) {
return (target, key, descriptor) => {
initializeTarget(target);
const testSuiteInstance: TestSuite = target.__testSuiteInstance;
if (testSuiteInstance.has(name)) {
throw new Error(`A test named "${name}" is already registered. Copy pasta much?`);
}

testSuiteInstance.set(name, generateTest(name, testCases, TestStatus.Normal, descriptor.value, timeout));
};
export function test(name?: string, testCases?: TestCase[], timeout: number = 2000) {
return generateDecoratorFunction(name, TestStatus.Normal, testCases, timeout);
}

/**
Expand All @@ -31,15 +23,7 @@ export function test(name: string, testCases?: TestCase[], timeout: number = 200
* @param timeout The test will automaticlaly fail if it has been running for longer than the specified timeout.
*/
export function ftest(name: string, testCases?: TestCase[], timeout: number = 2000) {
return (target, key, descriptor) => {
initializeTarget(target);
const testSuiteInstance: TestSuite = target.__testSuiteInstance;
if (testSuiteInstance.has(name)) {
throw new Error(`A test named "${name}" is already registered. Copy pasta much?`);
}

testSuiteInstance.set(name, generateTest(name, testCases, TestStatus.Focused, descriptor.value, timeout));
};
return generateDecoratorFunction(name, TestStatus.Focused, testCases, timeout);
}

/**
Expand All @@ -51,21 +35,27 @@ export function ftest(name: string, testCases?: TestCase[], timeout: number = 20
* @param timeout The test will automaticlaly fail if it has been running for longer than the specified timeout.
*/
export function xtest(name: string, testCases?: TestCase[], timeout: number = 2000) {
return generateDecoratorFunction(name, TestStatus.Ignored, testCases, timeout);
}

function initializeTarget(target: any) {
if (!target.__testSuiteInstance) { target.__testSuiteInstance = new TestSuite(); }
}

function generateDecoratorFunction(name: string, status: TestStatus, testCases: TestCase[], timeout: number) {
return (target, key, descriptor) => {
initializeTarget(target);
const testSuiteInstance: TestSuite = target.__testSuiteInstance;

name = name ? name : key;
if (testSuiteInstance.has(name)) {
throw new Error(`A test named "${name}" is already registered. Copy pasta much?`);
}

testSuiteInstance.set(name, generateTest(name, testCases, TestStatus.Ignored, descriptor.value, timeout));
testSuiteInstance.set(name, generateTest(name, testCases, status, descriptor.value, timeout));
};
}

function initializeTarget(target: any) {
if (!target.__testSuiteInstance) { target.__testSuiteInstance = new TestSuite(); }
}

function generateTest(name: string, testCases: TestCase[], status: TestStatus, testMethod: Function, timeout: number): Test | TestSuite {
return testCases
? generateTestsFromTestcases(name, testMethod, testCases, status, timeout)
Expand Down
20 changes: 9 additions & 11 deletions src/lib/decorators/testSuite.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,37 @@ import { TestSuite } from '../tests/testSuite';
*
* @param name Name of the test suite, displayed in the test report.
*/
export function testSuite<T extends { new(...args: any[]): {} }>(name: string): any {
return createTestSuiteDecoratorFactory<T>(name, TestStatus.Normal);
export function testSuite<T extends { new(...args: any[]): {} }>(name?: string): any {
return createTestSuiteDecoratorFactory<T>(TestStatus.Normal, name);
}

/**
* Marks a class as a focused test suite. If one or more test suites are marked as focused, only the those will be ran.
*
* @param name Name of the test suite, displayed in the test report.
*/
export function ftestSuite<T extends { new(...args: any[]): {} }>(name: string): any {
return createTestSuiteDecoratorFactory<T>(name, TestStatus.Focused);
export function ftestSuite<T extends { new(...args: any[]): {} }>(name?: string): any {
return createTestSuiteDecoratorFactory<T>(TestStatus.Focused, name);
}

/**
* Marks a class as an ignored test suite. Its tests will not be ran, but will still show up in the test report.
*
* @param name Name of the test suite, displayed in the test report.
*/
export function xtestSuite<T extends { new(...args: any[]): {} }>(name: string): any {
return createTestSuiteDecoratorFactory<T>(name, TestStatus.Ignored);
export function xtestSuite<T extends { new(...args: any[]): {} }>(name?: string): any {
return createTestSuiteDecoratorFactory<T>(TestStatus.Ignored, name);
}

function createTestSuiteDecoratorFactory<T extends { new(...args: any[]): {} }>(name: string, status: TestStatus) {
function createTestSuiteDecoratorFactory<T extends { new(...args: any[]): {} }>(status: TestStatus, name?: string) {
return (constructor: T) => {
name = name ? name : constructor.name;
(constructor as any).__testSuiteInstance = createTestSuite(constructor, name, status);
return constructor;
};
}

/**
* [WARNING] This class should be used for internal testing.
*/
export function createTestSuite<T>(constructor: new () => T, name: string, status: TestStatus): TestSuite {
function createTestSuite<T>(constructor: new () => T, name: string, status: TestStatus): TestSuite {
const context = new constructor();
const testSuiteInstance: TestSuite = (context as any).__testSuiteInstance;
testSuiteInstance.name = name;
Expand Down
6 changes: 5 additions & 1 deletion src/lib/utils/testsLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import * as tsnode from 'ts-node';
import { TestSuite } from '../tests/testSuite';

export class TestsLoader {
private static isTsnodeRegistered = false;
constructor(private logger?: Logger) { }

public async loadTests(root: string, patterns: string[], tsconfig: {}): Promise<TestSuite> {
// We register the tsnode compiler to transpile the test files
tsnode.register(tsconfig);
if (!TestsLoader.isTsnodeRegistered) {
tsnode.register(tsconfig);
TestsLoader.isTsnodeRegistered = true;
}

const files: Set<string> = new Set();
for (const pattern of patterns) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { test, beforeAll, beforeEach, afterEach, afterAll } from '../../../testyCore';
import { testSuite } from '../../../lib/decorators/testSuite.decorator';

@testSuite('Base Test Suite')
export class BaseTestSuite {
public beforeAllExecuted = [];
public beforeEachExecuted = [];
Expand Down
17 changes: 6 additions & 11 deletions src/spec/decorators/baseTestSuite/testWithBase.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { test } from '../../../lib/decorators/test.decorator';
import { createTestSuite, testSuite } from '../../../lib/decorators/testSuite.decorator';
import { TestStatus } from '../../../lib/testStatus';
import { TestSuiteWithBase, BaseTestSuite } from './testSuiteWithBaseTestSuite';
import { testSuite } from '../../../lib/decorators/testSuite.decorator';
import { expect } from '../../../testyCore';
import { TestsRunnerVisitor } from '../../../lib/tests/visitors/runnerVisitor';
import { NullLogger } from '../../utils/nullLogger';
import { Logger } from '../../../lib/logger/logger';
import { BaseTestSuite, TestSuiteWithBase } from './testSuiteWithBase';
import { TestSuiteTestsBase } from '../testSuiteTestsBase';

@testSuite('Test Suite With Base Test Suite Tests')
export class BeforeAfterDecoratorsTestSuite {
private logger: Logger = new NullLogger();
export class BeforeAfterDecoratorsTestSuite extends TestSuiteTestsBase {

@test('the base and the actual test suite before and after methods are called.')
private async trivialCase() {
// Arrange
const testSuite = createTestSuite(TestSuiteWithBase, 'Dummy Test Suite', TestStatus.Normal);
const testRunnerVisitor = new TestsRunnerVisitor(this.logger);
const testSuite = this.getTestSuiteInstance(TestSuiteWithBase);

// Act
const report = await testSuite.accept(testRunnerVisitor);
await testSuite.accept(this.visitor);

// Assert
expect.toBeEqual(testSuite.context.beforeAllExecuted[0], BaseTestSuite);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
import { test } from '../../../lib/decorators/test.decorator';
import { createTestSuite, testSuite } from '../../../lib/decorators/testSuite.decorator';
import { Logger } from '../../../lib/logger/logger';
import { TestResult } from '../../../lib/reporting/report/testResult';
import { TestCase } from '../../../lib/testCase';
import { TestsRunnerVisitor } from '../../../lib/tests/visitors/runnerVisitor';
import { TestStatus } from '../../../lib/testStatus';
import { expect } from '../../../testyCore';
import { NullLogger } from '../../utils/nullLogger';
import { testSuite } from '../../../lib/decorators/testSuite.decorator';
import { expect, TestCase, TestResult, beforeEach } from '../../../testyCore';
import { NormalBeforeAfterTestSuite } from './normalBeforeAfterTestSuite';
import { ThrowsDuringAfterAllTestSuite } from './throwsDuringAfterAllTestSuite';
import { ThrowsDuringAfterEachTestSuite } from './throwsDuringAfterEachTestSuite';
import { ThrowsDuringBeforeAllTestSuite } from './throwsDuringBeforeAllTestSuite';
import { ThrowsDuringBeforeEachTestSuite } from './throwsDuringBeforeEachTestSuite';
import { ThrowsDuringAfterEachTestSuite } from './throwsDuringAfterEachTestSuite';
import { ThrowsDuringAfterAllTestSuite } from './throwsDuringAfterAllTestSuite';
import { NullLogger } from '../../utils/nullLogger';
import { Logger } from '../../../lib/logger/logger';
import { TestsVisitor } from '../../../lib/tests/visitors/testVisitor';
import { Report } from '../../../lib/reporting/report/report';
import { TestsRunnerVisitor } from '../../../lib/tests/visitors/runnerVisitor';
import { TestSuite } from '../../../lib/tests/testSuite';


@testSuite('Before and After Decorators Test Suite')
export class BeforeAfterDecoratorsTestSuite {
// TODO: This test suite should extend TestSuiteTestsBase when #8 is fixed.
private logger: Logger = new NullLogger();
private visitor = new TestsRunnerVisitor(this.logger);
protected visitor: TestsVisitor<Report>;

@beforeEach()
private beforeEach() {
this.visitor = new TestsRunnerVisitor(this.logger);
}

@test('beforeAll, beforeEach, afterEach and afterAll are called the right amount of time.')
private async trivialCase() {
// Arrange
const testSuite = createTestSuite(NormalBeforeAfterTestSuite, 'Dummy Test Suite', TestStatus.Normal);
const testSuite = this.getTestSuiteInstance(NormalBeforeAfterTestSuite);

// Act
const report = await testSuite.accept(this.visitor);
Expand All @@ -40,9 +47,9 @@ export class BeforeAfterDecoratorsTestSuite {
new TestCase('afterEach throws, should return a failed test report', ThrowsDuringAfterEachTestSuite, 6),
new TestCase('afterAll throws, should return a failed test report', ThrowsDuringAfterAllTestSuite, 6),
])
private async beforeOfAfterMethodFails(testSuiteType: any, numberOfTests: number) {
private async beforeOfAfterMethodFails(testSuiteClass: any, numberOfTests: number) {
// Arrange
const testSuite = createTestSuite(testSuiteType, 'Dummy Test Suite', TestStatus.Normal);
const testSuite = this.getTestSuiteInstance(testSuiteClass);

// Act
const report = await testSuite.accept(this.visitor);
Expand All @@ -52,5 +59,9 @@ export class BeforeAfterDecoratorsTestSuite {
expect.toBeEqual(report.result, TestResult.Failure);
expect.toBeEqual(report.numberOfTests, numberOfTests, 'Expected all tests to be part of the report.');
}

protected getTestSuiteInstance(testClass: any): TestSuite {
return testClass.__testSuiteInstance;
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { test, beforeAll, beforeEach, afterEach, afterAll, TestCase } from '../../../testyCore';
import { xtest } from '../../../lib/decorators/test.decorator';
import { testSuite } from '../../../lib/decorators/testSuite.decorator';

@testSuite('Normal Before After Test Suite')
export class NormalBeforeAfterTestSuite {
public numberOfBeforeAllExecutions = 0;
public numberOfBeforeEachExecutions = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { test, xtest, afterAll, TestCase } from '../../../testyCore';
import { testSuite } from '../../../lib/decorators/testSuite.decorator';

@testSuite('Throws During After All Test Suite ')
export class ThrowsDuringAfterAllTestSuite {
@afterAll()
private afterAll() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { test, xtest, afterEach, TestCase } from '../../../testyCore';
import { testSuite } from '../../../lib/decorators/testSuite.decorator';

@testSuite('Throws During After Each Test Suite')
export class ThrowsDuringAfterEachTestSuite {
@afterEach()
private afterEach() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { test, xtest, beforeAll, TestCase } from '../../../testyCore';
import { testSuite } from '../../../lib/decorators/testSuite.decorator';

@testSuite('Throws During Before All Test Suite')
export class ThrowsDuringBeforeAllTestSuite {
@beforeAll()
private beforeAll() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test, xtest, beforeEach, TestCase } from '../../../testyCore';
import { test, xtest, beforeEach, TestCase, testSuite } from '../../../testyCore';

@testSuite('Throws During Before Each Test Suite')
export class ThrowsDuringBeforeEachTestSuite {
@beforeEach()
private beforeEach() {
Expand Down
Loading

0 comments on commit b47b12f

Please sign in to comment.