Правильное издевательство над S3 createPresignedPost с помощью Jest

avatar
Igor Shmukler
8 августа 2021 в 23:09
585
1
0

Я пытаюсь добавить модульные тесты в свой код. Пытаюсь справиться с функцией, обрабатывающей предварительно подписанные URL-адреса S3. Моя функция ниже.

'use strict';

const AWS = require('aws-sdk');

AWS.config.update({
  region: process.env.AWS_DEFAULT_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  }
});

const createPresignedPost = ({ key, contentType }) => {
  const s3 = new AWS.S3();
  const params = {
    Expires: 60,
    Bucket: process.env.AWS_BUCKET_NAME,
    Conditions: [['content-length-range', 100, 10000000]], // 100Byte - 10MB
    Fields: {
      'Content-Type': contentType,
      'Cache-Control': 'max-age=31536000',
      'Access-Control-Allow-Origin': '*',
      key
    }
  };

  return new Promise(async (resolve, reject) => {
    s3.createPresignedPost(params, (err, data) => {
      if (err) {
        reject(err);
      }
      resolve(data);
    });
  });
};

module.exports = createPresignedPost;

Как минимум, я просто хочу, чтобы в шутку считалось, что эта функция в какой-то степени покрыта модульными тестами, поэтому мои пороговые значения остаются выше минимального охвата, необходимого для CI, чтобы мой код мог собираться.

Я сделал следующее:

const mockedCreatePresignedPost = jest.fn(() => ({
  promise: jest.fn()
}));

jest.mock('aws-sdk', () => {
  return {
    S3: jest.fn(() => ({
      createPresignedPost: mockedCreatePresignedPost
    })),
    config: {
      update: jest.fn()
    }
  };
});
it('has to mock S3#createPresignedPost', /* async */ () => {
  process.env.AWS_BUCKET_NAME = 'test1';
  const params = {
    key: 'test2',
    contentType: 'application/json'
  };
  const s3params = {
    Bucket: 'test1',
    Conditions: [['content-length-range', 100, 10000000]],
    Expires: 60,
    Fields: {
      'Access-Control-Allow-Origin': '*',
      'Cache-Control': 'max-age=31536000',
      'Content-Type': params['contentType'],
      key: params['key']
    }
  };
  /* await */
  createPresignedPost(params);
  expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params);
});

Однако тесты не работают. Я получаю следующее несоответствие:

expect(jest.fn()).toHaveBeenCalledWith(...expected)

- Expected
+ Received

  {"Bucket": "test1", "Conditions": [["content-length-range", 100, 10000000]], "Expires": 60, "Fields": {"Access-Control-Allow-Origin": "*", "Cache-Control": "max-age=31536000", "Content-Type": "application/json", "key": "test2"}},
+ [Function anonymous],

Number of calls: 1

Почему у меня возникают проблемы со звонком createPresignedPost() с помощью await, как будто это должно работать? Как мне избавиться от этого "лишнего" [Function anonymous], чтобы мой тест прошел?

Источник

Ответы (1)

avatar
slideshowp2
9 августа 2021 в 03:29
2

Вы должны имитировать реализацию метода s3.createPresignedPost с обратным вызовом. Затем вы должны вызвать обратный вызов вручную с помощью макета Error или data.

.

Кроме того, createPresignedPost должен вызываться с двумя параметрами: params и анонимной функцией обратного вызова, вы можете использовать expect.any(Function) для ее представления.

Например,

index.js:

'use strict';

const AWS = require('aws-sdk');

AWS.config.update({
  region: process.env.AWS_DEFAULT_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
});

const createPresignedPost = ({ key, contentType }) => {
  const s3 = new AWS.S3();
  const params = {
    Expires: 60,
    Bucket: process.env.AWS_BUCKET_NAME,
    Conditions: [['content-length-range', 100, 10000000]], // 100Byte - 10MB
    Fields: {
      'Content-Type': contentType,
      'Cache-Control': 'max-age=31536000',
      'Access-Control-Allow-Origin': '*',
      key,
    },
  };

  return new Promise((resolve, reject) => {
    s3.createPresignedPost(params, (err, data) => {
      if (err) {
        reject(err);
      }
      resolve(data);
    });
  });
};

module.exports = createPresignedPost;

index.test.js:

const createPresignedPost = require('./');

const mockedCreatePresignedPost = jest.fn();

jest.mock('aws-sdk', () => {
  return {
    S3: jest.fn(() => ({
      createPresignedPost: mockedCreatePresignedPost,
    })),
    config: {
      update: jest.fn(),
    },
  };
});

describe('68705353', () => {
  it('has to mock S3#createPresignedPost', async () => {
    mockedCreatePresignedPost.mockImplementation((params, callback) => {
      callback(null, 'mocked data');
    });
    process.env.AWS_BUCKET_NAME = 'test1';
    const params = {
      key: 'test2',
      contentType: 'application/json',
    };
    const s3params = {
      Bucket: 'test1',
      Conditions: [['content-length-range', 100, 10000000]],
      Expires: 60,
      Fields: {
        'Access-Control-Allow-Origin': '*',
        'Cache-Control': 'max-age=31536000',
        'Content-Type': params['contentType'],
        key: params['key'],
      },
    };
    const actual = await createPresignedPost(params);
    expect(actual).toEqual('mocked data');
    expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params, expect.any(Function));
  });

  it('should handle error', async () => {
    mockedCreatePresignedPost.mockImplementation((params, callback) => {
      callback(new Error('network'));
    });
    process.env.AWS_BUCKET_NAME = 'test1';
    const params = {
      key: 'test2',
      contentType: 'application/json',
    };
    const s3params = {
      Bucket: 'test1',
      Conditions: [['content-length-range', 100, 10000000]],
      Expires: 60,
      Fields: {
        'Access-Control-Allow-Origin': '*',
        'Cache-Control': 'max-age=31536000',
        'Content-Type': params['contentType'],
        key: params['key'],
      },
    };
    await expect(createPresignedPost(params)).rejects.toThrowError('network');
    expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params, expect.any(Function));
  });
});

результат теста:

 PASS  examples/68705353/index.test.js (8.834 s)
  68705353
    ✓ has to mock S3#createPresignedPost (4 ms)
    ✓ should handle error (4 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 index.js |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        9.648 s
Adrián Juárez
28 октября 2021 в 15:29
0

Я попробовал ваше решение, и оно работает, если я создаю s3 внутри тестируемой функции (как вы сделали в createPresignedPost внутри index.js), но не если я определяю его как глобальную переменную: он говорит, что все члены s3 не определены. В моем случае у меня есть больше функций, которые используют s3, но я думаю, что было бы лучше сделать глобальный s3 и использовать его в каждой функции, чем создавать новый каждый раз, когда я вызываю функцию. Вы знаете, возможно ли это сделать и пройти тесты?