Testing
The SDK includes tools that facilitate easier coverage of app configuration
through integration tests.
For the 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
Directorymocks Directory to store manual mocks
- api.js
- axios.js
Directorytests
- tests.js Test suit 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. Thetests.js
file acts as the main configuration or entry point for running your integration test.
Sample of the Test Config
Main file
const {runAllTests} = require('@crowdin/app-project-module/out/app-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', () => ({ ...require('../__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 () => require('../app').configuration, testSuiteConfig});
Deep Dive Into the Details
Simplified example to understand the main point of how it works:
// some high level mocks, like crowdin-api-client// then described the test-suit
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}); } }); }});