# Testing

The SDK includes tools that facilitate easier coverage of app `configuration` through integration tests.

For the [Project Integration](/app-project-module/project-integration/) module, there are predefined test suites available that cover the following methods:

- `getIntegrationFiles()`
- `updateCrowdin()`
- `updateIntegration()`

This test tool runs three tests, one for each of the methods listed above. Mocks of the `crowdin-api-client` are stored on the module side.

Tests will run sequentially. The first test checks the `getIntegrationFiles()` method, verifying the structure of the returned tree. Following this, the `updateCrowdin()` test runs, performing checks for file and folder creation. These files are stored in memory and passed in the request to the next test, `updateIntegration()`.

Developers can add their own matchers to each test to ensure that the tests meet their specific requirements.

## Important Prerequisites

Before you begin implementing tests, it's crucial to ensure the following prerequisites are met to facilitate a smooth testing process:

- **Separation of Concerns**: The app configuration and the web server startup process should be isolated and placed in different files. This separation enhances modularity and makes the testing environment more manageable.
- **Third-party Calls**: All third-party API calls, such as `axios.get()`, should be abstracted and placed in a separate file. This approach simplifies mocking these calls during testing and improves code maintainability.

## Sample file structure of the app

Below is an example of the file structure for your project, designed to support integration testing and mock implementations:

- index.js Entry point of the application
  - app.js Core application logic
  - api.js Handles external API calls
  - __mocks__ Directory to store manual mocks
    - api.js
    - axios.js
  - __tests__
    - tests.js Test suite configuration file
**Annotations:**

- **`__mocks__` Directory**: This is where you store manual mocks for your modules and third-party libraries.
- **`__tests__` Directory**: Contains the test suites for your application. The `tests.js` file acts as the main configuration or entry point for running your integration test.

## Sample of the Test Config

Main file

```js title="__tests__/index.js" {5,10,18,20,35,37,44,48,50} wrap
import { runAllTests } from '@crowdin/app-project-module/test';

const mockPutEmailTranslation = jest.fn(async () => {});

// Manual mock of API calls to integration service.
jest.mock('../api');

const testSuiteConfig = {
  projectIntegration: {
    // Runs before start first test. You may put some extra mocks to check them later.
    beforeAll: () => {
      jest.mock('../api', () => ({
        ...jest.requireActual('../__mocks__/api'),
        putEmailTranslation: mockPutEmailTranslation
      }));
    },
    getIntegrationFiles: {
      // Actions at the beginning of test and before run the getIntegrationFiles() method.
      setup: () => {},
      // Describe the array with expected tree elements and their expected attributes.
      expected: [
        {
          id: 1,
          name: 'File 1',
          type: 'html'
        },
        {
          id: 2,
          name: 'File 2',
          type: 'html'
        }
      ]
    },
    updateCrowdin: {
      // Actions at the beginning of test and before run the updateCrowdin() method.
      setup: () => {},
      // If it's not described by default it expects that file name would be `${node.name}.${node.type}`.
      expected: {
        files: [
          { name: '1.xml', path: '/1.xml' },
          { name: '2.xml', path: '/2.xml' }
        ]
      },
      // Make extra-checks if needed.
      extraChecks: ({request}) => {}
    },
    updateIntegration: {
      // Actions at the beginning of test and before run the updateIntegration() method.
      setup: () => {},
      // Make some extra-checks using mocks that was defined earlier.
      extraChecks: ({request}) => {
        expect(Object.keys(request).length).toBe(
          testSuiteConfig.projectIntegration.getIntegrationFiles.expected.length
        );
        expect(mockPutEmailTranslation.mock.calls.length).toBe(
          testSuiteConfig.projectIntegration.getIntegrationFiles.expected.length
        );
      }
    }
  }
};

runAllTests({
  getAppConfig: async () => (await import('../app')).configuration,
  testSuiteConfig
});
```

## Deep Dive Into the Details

Simplified example to understand the main point of how it works:

```js
// some high level mocks, like crowdin-api-client
// then describe the test suite

const integrationTestConfig = testSuiteConfig.projectIntegration;

describe('Run integration module tests', () => {
  beforeAll(async () => {
    await integrationTestConfig.beforeAll();
  });

  test('getIntegrationFiles()', async () => {
    if (integrationTestConfig.getIntegrationFiles?.setup) {
      await integrationTestConfig.getIntegrationFiles.setup();
    }

    const appConfig = await getAppConfig();
    // main test logic and checks
    // ...

    if (integrationTestConfig.getIntegrationFiles?.extraChecks) {
      await integrationTestConfig.getIntegrationFiles.extraChecks();
    }
  });

  if (!integrationTestConfig.updateCrowdin?.disabled) {
    test('updateCrowdin()', async () => {
      if (integrationTestConfig.updateCrowdin?.setup) {
        await integrationTestConfig.updateCrowdin.setup();
      }

      const appConfig = await getAppConfig();
      // main test logic and checks
      // ...

      if (integrationTestConfig.updateCrowdin?.extraChecks) {
        await integrationTestConfig.updateCrowdin.extraChecks({request});
      }
    });
  }

  if (!integrationTestConfig.updateIntegration?.disabled) {
    test('updateIntegration()', async () => {
      if (integrationTestConfig.updateIntegration?.setup) {
        await integrationTestConfig.updateIntegration.setup();
      }

      const appConfig = await getAppConfig();
      // main test logic and checks
      // ...

      if (integrationTestConfig.updateIntegration?.extraChecks) {
        await integrationTestConfig.updateIntegration.extraChecks({request});
      }
    });
  }
});
```

## Useful resources

- [Jest docs](https://jestjs.io/docs/getting-started)
- [Manual Mocks](https://jestjs.io/docs/manual-mocks)
- [Using Matchers](https://jestjs.io/docs/using-matchers)