Написание серверного кода — например, веб-сервисов или чего-то еще — с помощью функций AWS Lambda невероятно просто, особенно когда вы выбираете Node.js в качестве своего предпочтительного оружия. Количество кода, необходимого для запуска, настолько мало, что кажется почти волшебным. Однако по мере того, как вы строите свою лямбду, сложность быстро поднимает голову, и вскоре вы почувствуете необходимость добавить несколько тестов.

Модульное тестирование является частью рабочего процесса любого хорошего разработчика, но я считаю, что оно особенно важно при работе с языками с динамической типизацией, такими как ванильный Javascript. Его свободная типизация делает разработку быстрой, но также создает определенную степень неопределенности при внесении изменений или рефакторинге. Хорошее тестовое покрытие может компенсировать это и позволить вам работать быстрее. Если вы сможете имитировать зависимости вашей Lambda, вы будете вполне уверены, что ваш успешный модульный тест представляет собой конечный производственный код.

Внедрение зависимости

«Внедрение зависимостей» — это несколько пугающий термин, используемый в разработке программного обеспечения для описания чего-то довольно простого:

Внедрение зависимостей — это метод программирования, который делает класс независимым от его зависимостей. Это достигается путем отделения использования объекта от его создания.

Это наиболее полезно при применении в контексте модульного тестирования, поскольку позволяет вам имитировать зависимости, которые не должны быть активны во время тестов.

В функциях Node.js Lambda зависимости импортируются с помощью функции require(). Он создает константу в области видимости функции, указывающую на какой-то внешний код. По умолчанию вы делаете это на верхнем уровне вашего файла Node.js, эффективно делая зависимость глобально доступной для указанного файла. Рассмотрим этот фрагмент, где мы импортируем AWS SDK и создаем новый экземпляр DynamoDB DocumentClient:

const AWS = require('aws-sdk') 
const documentClient = new AWS.DynamoDB.DocumentClient()

Что происходит, когда вы используете код модульного тестирования, который импортирует указанную выше зависимость? В этом случае ваш тест установит активное соединение с DynamoDB и, возможно, начнет чтение и запись данных в нее! Хотя вы можете возразить, что это тест сам по себе, эта ситуация далека от идеальной. Каждый вызов модульного теста будет

  • потенциально нести расходы
  • записывать данные в действующую базу данных, возможно, нарушая ее согласованность
  • быть медленным

Пост Ричарда Хаятта Medium от 2016 года актуален и сегодня, поскольку в нем описывается, как сделать загрузку зависимостей асинхронной и внедряемой с помощью экспорта. объект для хранения и ссылки на зависимости.

exports.deps = () => { 
  const AWS = require('aws-sdk') 
  const documentClient = new AWS.DynamoDB.DocumentClient() 
  return Promise.resolve({ 
    dynamoBatchWrite: params => documentClient.batchWrite(params).promise() 
  }) 
}

Фактический импорт зависимостей заключен в область действия функции deps и делается асинхронным путем помещения словаря результатов в промис. Эта асинхронность позволяет нам перезаписывать функцию deps во время тестов, оставляя ее как есть в рабочей среде.

Рабочий код будет просто ждать зависимостей вверху, после чего вы сможете получить доступ к полностью построенным зависимостям:

exports.handler = async event => { 
  const deps = await exports.deps() 
  ... 
}

Теперь для теста:

require('chai').should() 
const lambda = require('../index') 
const sinon = require('sinon')
 
describe('importOPML', () => { 
  beforeEach('mock dependencies', () => { 
    const mockWriter = sinon.mock() 
    mockWriter.resolves({ UnprocessedItems: [] }) 
    lambda.deps = () => Promise.resolve({ 
      dynamoBatchWrite: mockWriter 
    }) 
  }) 
  it('should succeed with empty opml', async () => { 
      // Using lambda here, will call the version that uses the mocked DynamoDB writer. 
  } 
})

Это тест Chai, в котором Синон используется для насмешек, но предпосылка та же. Перед запуском каждого тестового блока выполняется блок beforeEach, который подготавливает лямбду с фиктивными зависимостями.

Вот и все. Вы отправляетесь на гонки!

Первоначально опубликовано на jarroo.com 9 декабря 2018 г.