vitalishapovalov / jest-decorated

Decorators library for writing jest-based tests
https://vitalishapovalov.github.io/jest-decorated/
MIT License
49 stars 4 forks source link

Is there some way to using jest-decorated API to return some info? #312

Open amrsalem1 opened 3 years ago

amrsalem1 commented 3 years ago

is there some way that can be used so I can extract some info from test spec files, like name of the test suites, test case titles...etc without executing any test?

vitalishapovalov commented 3 years ago

Yes, there are some ways, but they are poorly documented and should be improved (refactored a bit). I'm planning to do this, probably by the end of this week (docs/readme update & example & misc refactor for simplifying the API).

Currently, you can use the @RunWith decorator and implement your own TestRunner. Here is it's interface to implement:

interface ITestRunner {
  registerMocks(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void;
  registerAutoCleared(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void;
  registerLazyModules(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void;
  registerMockFnsAndSpies(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void;
  registerHooks(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void;
  registerTestsInJest(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void;
}

ATM, you must implement ALL of the methods, and add more functionality after. So, the initial implementation will look like this:

class MyTestRunner implements ITestRunner {

  public constructor(protected readonly defaultTestsRunner: ITestRunner) {}

  public registerMocks(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void {
    this.defaultTestsRunner.registerMocks(describeRunner, parentDescribeRunner);
  }
  public registerAutoCleared(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void {
    this.defaultTestsRunner.registerAutoCleared(describeRunner, parentDescribeRunner);
  }
  public registerLazyModules(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void {
    this.defaultTestsRunner.registerLazyModules(describeRunner, parentDescribeRunner);
  }
  public registerMockFnsAndSpies(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void {
    this.defaultTestsRunner.registerMockFnsAndSpies(describeRunner, parentDescribeRunner);
  }
  public registerHooks(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void {
    this.defaultTestsRunner.registerHooks(describeRunner, parentDescribeRunner);
  }
  public registerTestsInJest(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void {
    this.defaultTestsRunner.registerTestsInJest(describeRunner, parentDescribeRunner);
  }

}

And then, you can starting doing your own things:

class MyTestRunner implements ITestRunner {

  // ...

  public registerTestsInJest(describeRunner: IDescribeRunner, parentDescribeRunner?: IDescribeRunner): void {
    this.processTests(describeRunner); // this line is executed before tests are passed to jest
    this.defaultTestsRunner.registerTestsInJest(describeRunner, parentDescribeRunner); // this line is registering tests in jest
  }

  protected processTests(describeRunner: IDescribeRunner): void {
    const testsService = describeRunner.getTestsService();

    // log info about tests, set your own metadata, change anything, etc.
    for (const testEntity of testsService.getTests()) {
      console.log(testEntity); // TestEntity { name, description, timeout, metadata, dataProviders, testType }
      testEntity.description += " my label"; // modify test's description
      testEntity.setMetadata("myKey", { myVal: "foo" }); // add some custom data to test entity
      testEntity.setTestType("skip"); // or skip test at all, if you want
    }

    testsService.registerPreProcessor(
      // callback to be executed for each test (during the jest tests execution), before running the test itself
      (preProcessorData: PreProcessorData) => {
        // here you can do any side effects, but you shouldn't change any info about the test itself (name, description, type)
        const { testEntity, args, clazzInstance } = preProcessorData;
        // you can also override this data, by returning the same object
        return preProcessorData;
      },
      // order of the callback in the callbacks chain, if it matters
      1_000,
    );

    testsService.registerPostProcessor(
      // callback to be executed for each test (during the jest tests execution), after running the test itself
      (preProcessorResult: unknown, testError?: Error) => {
        // preProcessorResult is an entity, returned by your test-method (usually test returns nothing, and the value is undefined)
        console.log(preProcessorResult);
        // testError is a test error; regardless of the post-processor result, this function error will be thrown (test has failed)
        console.log(testError);
      },
      // order of the callback in the callbacks chain, if it matters
      1_000,
    );
  }

}

When you're ready - start using your runner:

@Describe()
@RunWith(MyTestRunner)
class MyTest {
  // ...
}

Typings can be found in the @jest-decorated/shared package:

import type { ITestRunner, PreProcessorData, IDescribeRunner, TestEntity } from "@jest-decorated/shared";
vitalishapovalov commented 3 years ago

Docs updated.

Please, refer to writing custom test runner and writing custom decorators guides.

amrsalem1 commented 2 years ago

@vitalishapovalov thanks for sharing this, but could you provide a sample for a snippet on how exactly I can run one single test that will run in dry mode and get a list of names of specific annotation values such as @Test/@Describe...etc values for other existing tests

vitalishapovalov commented 2 years ago

I'm not 100% sure that I've understood the issue, maybe you can provide some more details or examples of what exactly you want to achieve? Regarding the Dry mode - you mean, bypass all of the additional functionality and simply execute existing tests/hooks?