Colocation

Contents

Optionally place tests next to your source code file.

Intro

So far, all tests have been placed under src/jest, cleanly separated from the source code. It is possible to place the tests in the same directory as the source code, but it has some drawbacks and complexity.

We will cover it all in this chapter.

Jest configuration

In order for Jest to find and run the colocated tests, the test files must be included in the testMatch configuration. This can be done by adding the colocated test files as include patterns in the Jest configuration files:

jest.config.ts
import {
  // ...
  DIR_SRC_ASSETS,
  DIR_SRC_STATIC,
} from './tsup/constants';

const clientSideConfig: Config.InitialProjectOptions = {
  // ...
  testMatch: [
    // ... other testMatches
    `<rootDir>/${DIR_SRC_ASSETS}/${AND_BELOW}/${TEST_FILES}`,
    `<rootDir>/${DIR_SRC_STATIC}/${AND_BELOW}/${TEST_FILES}`,
  ],
};

const serverSideConfig: Config.InitialProjectOptions = {
  // ...
  testMatch: [
    // ... other testMatches
    `<rootDir>/${DIR_SRC}/${AND_BELOW}/${TEST_FILES}`,
  ],
  testPathIgnorePatterns: [
    `<rootDir>/${DIR_SRC_ASSETS}/`,
    `<rootDir>/${DIR_SRC_STATIC}/`,
  ],
};

Build system

So far, we have been using Tsup to build the sources. If the tests are placed in the same directories as the source code, the build system will process the tests as source code. This is not what we want. We want the build system to ignore the tests.

This can be done by adding the test files as ignore patterns in the Tsup configuration files.

tsup/constants.ts
export const TEST_EXT = `{spec,test}.{ts,tsx}`;
tsup/client-asset.ts
const FILES_ASSETS = globSync(`${DIR_SRC_ASSETS}/${AND_BELOW}/*.${GLOB_EXTENSIONS_ASSETS}`, {
  ignore: globSync(`${DIR_SRC_ASSETS}/${AND_BELOW}/*.${TEST_EXT}`
})
  // Windows compatibility
  .map(s => s.replaceAll('\\', '/'));
tsup/client-static.ts
const FILES_ASSETS = globSync(`${DIR_SRC_STATIC}/${AND_BELOW}/*.${GLOB_EXTENSIONS_STATIC}`, {
  ignore: globSync(`${DIR_SRC_STATIC}/${AND_BELOW}/*.${TEST_EXT}`
})
  // Windows compatibility
  .map(s => s.replaceAll('\\', '/'));
tsup/server.ts
const FILES_SERVER = globSync(
  `${DIR_SRC}/${AND_BELOW}/*.${GLOB_EXTENSIONS_SERVER}`,
  {
    absolute: false,
    ignore: globSync(`${DIR_SRC_ASSETS}/${AND_BELOW}/*.${GLOB_EXTENSIONS_SERVER}`).concat(
      globSync(`${DIR_SRC_STATIC}/${AND_BELOW}/*.${GLOB_EXTENSIONS_SERVER}`),
      globSync(`${DIR_SRC}/${AND_BELOW}/*.${TEST_EXT}`), // Avoid compiling test files
    )
  }
)
  // Windows compatibility
  .map(s => s.replaceAll('\\', '/'));

Type checking

Test files can contain things like jest functions in the global scope that are not available in the source code. This will cause the type checker to fail.

When running type checks these tsconfig.json files are used:

  • tsconfig.json

  • src/main/resources/assets/tsconfig.json

  • src/main/resources/static/tsconfig.json

  • src/jest/client/tsconfig.json

  • src/jest/server/tsconfig.json

, each of them bringing their own include and exclude patters.

In the current configuration include patterns with *.ts and *.tsx are used.

Obviously those patterns also match test files ending with *.test.ts or *.test.tsx.

Here is how to make sure test files a type checked as test files, not as source files:

Exclude from source code type checking

tsconfig.json
{
  // ... other settings
  "exclude": [
    // ... other excludes
    "**/*.spec.ts",
    "**/*.test.ts",
    "**/*.spec.tsx",
    "**/*.test.tsx"
  ]
}
src/main/resources/assets/tsconfig.json
{
  // ... other settings
  "exclude": [
    // ... other excludes
    "./**/*.spec.ts",
    "./**/*.test.ts",
    "./**/*.spec.tsx",
    "./**/*.test.tsx"
  ]
}
src/main/resources/static/tsconfig.json
{
  // ... other settings
  "exclude": [
    // ... other excludes
    "./**/*.spec.ts",
    "./**/*.test.ts",
    "./**/*.spec.tsx",
    "./**/*.test.tsx"
  ]
}

Include in test code type checking

src/jest/client/tsconfig.json
// ... other settings
  "include": [
    // ... other includes
    "../../main/resources/assets/**/*.spec.ts",
    "../../main/resources/assets/**/*.spec.tsx",
    "../../main/resources/assets/**/*.test.ts",
    "../../main/resources/assets/**/*.test.tsx",
    "../../main/resources/static/**/*.spec.ts",
    "../../main/resources/static/**/*.spec.tsx",
    "../../main/resources/static/**/*.test.ts",
    "../../main/resources/static/**/*.test.tsx",
  ]
src/jest/server/tsconfig.json
// ... other settings
  "include": [
    // ... other includes
    "../../main/resources/**/*.spec.ts",
    "../../main/resources/**/*.spec.tsx",
    "../../main/resources/**/*.test.ts",
    "../../main/resources/**/*.test.tsx",
  ],
  "exclude": [
    // ... other excludes
    "../../main/resources/assets/**/*.*",
    "../../main/resources/static/**/*.*",
  ]

IDE

Typically, IDEs use the closest tsconfig.json to resolve import paths and global types.

Which means that a test file colocated with source code is treated as source code by the IDE.

There may be a couple solutions to this:

Adding support for test syntax to all source files.

This is a bad option, because it may cause runtime problems if a developer adds test syntax in a source file, and get no warnings while coding, type checking or building.

In test files: Use relative imports and avoid globals.

This can be achieved by importing jest globals directly from @jest/globals.

/src/main/resources/**/any.test.ts
import {
  afterAll,
  afterEach,
  beforeAll,
  beforeEach,
  describe,
  expect,
  jest,
  test as it,
  // ...
} from '@jest/globals';

Summary

This wraps up the tutorial, we hope you enjoyed it. Visit the tutorials section on our Developer Portal for more.


Contents

Contents