Модульное тестирование облачных функций

На этой странице описаны передовые методы и инструменты для написания модульных тестов для ваших функций, таких как тесты, которые будут частью системы непрерывной интеграции (CI). Чтобы упростить тестирование, Firebase предоставляет SDK Firebase Test для облачных функций. Он распространяется на НПМ, firebase-functions-test , и является компаньоном тест SDK для firebase-functions . Пакет SDK Firebase Test для облачных функций:

  • Заботится о соответствующей установке и демонтаж для тестов, таких как установка и отключения переменных окружения , необходимых firebase-functions .
  • Создает образцы данных и контекст события, поэтому вам нужно указать только те поля, которые имеют отношение к вашему тесту.

Испытательная установка

Установить как firebase-functions-test и мокко , в рамках тестирования, запустив следующие команды в своих функциях папки:

npm install --save-dev firebase-functions-test
npm install --save-dev mocha

Далее создайте test папку внутри папки функций, создать новый файл в нем для вашего тестового кода, и назвать это что - то вроде index.test.js .

И, наконец, изменить functions/package.json добавить следующее:

"scripts": {
  "test": "mocha --reporter spec"
}

После того, как вы написали тесты, вы можете запустить их, выполнив npm test в каталоге функций.

Инициализация Firebase Test SDK для облачных функций

Есть два способа использования firebase-functions-test :

  1. Режим онлайн (рекомендуется): Писать тесты , которые взаимодействуют с проектом Firebase , посвященные тестированию , так что запись в базе данных, пользователь создает и т.д. будет на самом деле произошли, и ваш тестовый код может проверить результаты. Это также означает, что другие SDK Google, используемые в ваших функциях, также будут работать.
  2. Автономный режим: запись и офлайновые разрозненные единичные тесты без каких - либо побочных эффектов. Это означает, что любые вызовы методов, которые взаимодействуют с продуктом Firebase (например, запись в базу данных или создание пользователя), должны быть заглушены. Использование автономного режима обычно не рекомендуется, если у вас есть функции Cloud Firestore или Realtime Database, поскольку это значительно увеличивает сложность вашего тестового кода.

Инициализировать SDK в онлайн-режиме (рекомендуется)

Если вы хотите , чтобы тесты записи , которые взаимодействуют с тестовым проектом, вы должны указать значение конфигурации проекта, которые необходимы для инициализации приложения через firebase-admin , и путь к файлу ключ учетной записи службы.

Чтобы получить значения конфигурации вашего проекта Firebase:

  1. Откройте параметры проекта в консоли Firebase .
  2. В ваших приложениях, выберите нужное приложение.
  3. На правой панели выберите вариант загрузки файла конфигурации для приложений iOS и Android.

    Для веб - приложений, выберите Config для отображения значений конфигурации.

Чтобы создать ключевой файл:

  1. Откройте панель служебных учетных записей в Google Cloud Console.
  2. Выберите учетную запись службы по умолчанию App Engine, а также использовать меню опций на право выбора Создать ключ.
  3. При появлении запроса выберите JSON для типа ключа, и нажмите кнопку Создать.

После сохранения ключевого файла инициализируем SDK:

// At the top of test/index.test.js
const test = require('firebase-functions-test')({
  databaseURL: 'https://my-project.firebaseio.com',
  storageBucket: 'my-project.appspot.com',
  projectId: 'my-project',
}, 'path/to/serviceAccountKey.json');

Инициализировать SDK в автономном режиме

Если вы хотите писать полностью автономные тесты, вы можете инициализировать SDK без каких-либо параметров:

// At the top of test/index.test.js
const test = require('firebase-functions-test')();

Мокирующие значения конфигурации

Если вы используете functions.config() в вашей функции коде, вы можете издеваться значением конфигурации. Например, если functions/index.js содержит следующий код:

const functions = require('firebase-functions');
const key = functions.config().stripe.key;

Затем вы можете издеваться над значением внутри тестового файла следующим образом:

// Mock functions config values
test.mockConfig({ stripe: { key: '23wr42ewr34' }});

Импорт ваших функций

Чтобы импортировать функции, использование require , чтобы импортировать основную функцию файл в качестве модуля. Не забудьте сделать это только после инициализации firebase-functions-test , и насмешливого значения конфигурации.

// after firebase-functions-test has been initialized
const myFunctions = require('../index.js'); // relative path to functions code

Если вы инициализируетесь firebase-functions-test в автономном режиме , и у вас есть admin.initializeApp() в вашей функции коде, то вам необходимо незавершенным его перед импортом ваших функций:

// If index.js calls admin.initializeApp at the top of the file,
// we need to stub it out before requiring index.js. This is because the
// functions will be executed as a part of the require process.
// Here we stub admin.initializeApp to be a dummy function that doesn't do anything.
adminInitStub = sinon.stub(admin, 'initializeApp');
// Now we can require index.js and save the exports inside a namespace called myFunctions.
myFunctions = require('../index');

Тестирование фоновых (не HTTP) функций

Процесс тестирования функций, отличных от HTTP, включает следующие шаги:

  1. Обертка функции вы хотели бы проверить с test.wrap методом
  2. Построить тестовые данные
  3. Вызовите обернутую функцию с созданными вами тестовыми данными и любыми полями контекста события, которые вы хотите указать.
  4. Делайте утверждения о поведении.

Сначала оберните функцию, которую хотите протестировать. Скажем , у вас есть функция в functions/index.js под названием makeUppercase , который вы хотели бы проверить. Написать в functions/test/index.test.js

// "Wrap" the makeUpperCase function from index.js
const myFunctions = require('../index.js');
const wrapped = test.wrap(myFunctions.makeUppercase);

wrapped это функция , которая вызывает makeUppercase , когда она вызывается. wrapped принимает 2 параметра:

  1. данные (обязательно): данные для отправки makeUppercase . Это напрямую соответствует первому параметру, отправленному в обработчик функции, который вы написали. firebase-functions-test предоставляет методы для построения данных пользовательских данных или примера.
  2. eventContextOptions (дополнительно): Поля контекста событий , которые вы хотели бы задать. Контекст события - это второй параметр, отправляемый обработчику функции, который вы написали. Если вы не включаете eventContextOptions параметра при вызове wrapped , контекст события по - прежнему генерируется чувственными полей. Вы можете переопределить некоторые из созданных полей, указав их здесь. Обратите внимание, что вам нужно включить только те поля, которые вы хотите переопределить. Будут созданы все поля, которые вы не переопределили.
const data = … // See next section for constructing test data

// Invoke the wrapped function without specifying the event context.
wrapped(data);

// Invoke the function, and specify params
wrapped(data, {
  params: {
    pushId: '234234'
  }
});

// Invoke the function, and specify auth and auth Type (for real time database functions only)
wrapped(data, {
  auth: {
    uid: 'jckS2Q0'
  },
  authType: 'USER'
});

// Invoke the function, and specify all the fields that can be specified
wrapped(data, {
  eventId: 'abc',
  timestamp: '2018-03-23T17:27:17.099Z',
  params: {
    pushId: '234234'
  },
  auth: {
    uid: 'jckS2Q0' // only for real time database functions
  },
  authType: 'USER' // only for real time database functions
});

Создание тестовых данных

Первый параметр обернутой функции - это тестовые данные, с которыми вызывается базовая функция. Есть несколько способов создания тестовых данных.

Использование пользовательских данных

firebase-functions-test имеет ряд функций для построения данных , необходимых для проверки ваших функций. Например, можно использовать test.firestore.makeDocumentSnapshot создать Firestore DocumentSnapshot . Первый аргумент данные, а второй аргумент полный ссылочный путь, и есть необязательный третий аргумент для других свойств снимка можно указать.

// Make snapshot
const snap = test.firestore.makeDocumentSnapshot({foo: 'bar'}, 'document/path');
// Call wrapped function with the snapshot
const wrapped = test.wrap(myFunctions.myFirestoreDeleteFunction);
wrapped(snap);

Если вы тестируете onUpdate или onWrite функции, вам необходимо создать два снимка: один для перед государством и один для после государства. Затем вы можете использовать makeChange метод для создания Change объект с этими снимками.

// Make snapshot for state of database beforehand
const beforeSnap = test.firestore.makeDocumentSnapshot({foo: 'bar'}, 'document/path');
// Make snapshot for state of database after the change
const afterSnap = test.firestore.makeDocumentSnapshot({foo: 'faz'}, 'document/path');
const change = test.makeChange(beforeSnap, afterSnap);
// Call wrapped function with the Change object
const wrapped = test.wrap(myFunctions.myFirestoreUpdateFunction);
wrapped(change);

Смотрите ссылку API для аналогичных функций для всех других типов данных.

Использование данных примера

Если вам не нужно настраивать данные , используемые в ваших тестах, затем firebase-functions-test предлагает методы для создания примера данных для каждого типа функции.

// For Firestore onCreate or onDelete functions
const snap = test.firestore.exampleDocumentSnapshot();
// For Firestore onUpdate or onWrite functions
const change = test.firestore.exampleDocumentSnapshotChange();

Смотрите ссылку API для методов получения примера данных для каждого типа функции.

Использование заглушенных данных (для автономного режима)

Если вы инициализирован SDK в автономном режиме, а также тестируют функцию базы данных Cloud Firestore или в реальном времени, вы должны использовать простой объект с заглушками вместо создания фактического DocumentSnapshot или DataSnapshot .

Допустим, вы пишете модульный тест для следующей функции:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

Внутри функции, snap используется дважды:

  • snap.val()
  • snap.ref.parent.child('uppercase').set(uppercase)

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

// The following lines creates a fake snapshot, 'snap', which returns 'input' when snap.val() is called,
// and returns true when snap.ref.parent.child('uppercase').set('INPUT') is called.
const snap = {
  val: () => 'input',
  ref: {
    parent: {
      child: childStub,
    }
  }
};
childStub.withArgs(childParam).returns({ set: setStub });
setStub.withArgs(setParam).returns(true);

Утверждения

После инициализации SDK, упаковки функций и построения данных вы можете вызывать функции в оболочке с созданными данными и делать утверждения о поведении. Вы можете использовать библиотеку , такие как Chai для принятия этих утверждений.

Создание утверждений в онлайн-режиме

Если инициализируется Firebase Test SDK для облачных функций в режиме реального времени , вы можете утверждать , что желаемые действия (такие как запись базы данных) имеют место с помощью firebase-admin SDK.

В приведенном ниже примере утверждается, что INPUT был записан в базу данных тестового проекта.

// Create a DataSnapshot with the value 'input' and the reference path 'messages/11111/original'.
const snap = test.database.makeDataSnapshot('input', 'messages/11111/original');

// Wrap the makeUppercase function
const wrapped = test.wrap(myFunctions.makeUppercase);
// Call the wrapped function with the snapshot you constructed.
return wrapped(snap).then(() => {
  // Read the value of the data at messages/11111/uppercase. Because `admin.initializeApp()` is
  // called in functions/index.js, there's already a Firebase app initialized. Otherwise, add
  // `admin.initializeApp()` before this line.
  return admin.database().ref('messages/11111/uppercase').once('value').then((createdSnap) => {
    // Assert that the value is the uppercased version of our input.
    assert.equal(createdSnap.val(), 'INPUT');
  });
});

Создание утверждений в автономном режиме

Вы можете сделать утверждения об ожидаемом возвращаемом значении функции:

const childParam = 'uppercase';
const setParam = 'INPUT';
// Stubs are objects that fake and/or record function calls.
// These are excellent for verifying that functions have been called and to validate the
// parameters passed to those functions.
const childStub = sinon.stub();
const setStub = sinon.stub();
// The following lines creates a fake snapshot, 'snap', which returns 'input' when snap.val() is called,
// and returns true when snap.ref.parent.child('uppercase').set('INPUT') is called.
const snap = {
  val: () => 'input',
  ref: {
    parent: {
      child: childStub,
    }
  }
};
childStub.withArgs(childParam).returns({ set: setStub });
setStub.withArgs(setParam).returns(true);
// Wrap the makeUppercase function.
const wrapped = test.wrap(myFunctions.makeUppercase);
// Since we've stubbed snap.ref.parent.child(childParam).set(setParam) to return true if it was
// called with the parameters we expect, we assert that it indeed returned true.
return assert.equal(wrapped(snap), true);

Вы можете также использоваться Синоне шпионов , чтобы утверждать , что некоторые методы были вызваны, и с параметрами , которые вы ожидаете.

Тестирование функций HTTP

Чтобы проверить HTTP OnCall функции, использовать тот же подход, что и тестирования фоновых функций .

При тестировании HTTP onRequest функции, вы должны использовать firebase-functions-test , если:

  • Вы можете использовать functions.config()
  • Ваша функция взаимодействует с проектом Firebase или другими API Google, и вы хотите использовать настоящий проект Firebase и его учетные данные для своих тестов.

Функция HTTP onRequest принимает два параметра: объект запроса и объект ответа. Вот как вы можете проверить addMessage() пример функции :

  • Переопределить функцию переадресации в объект ответа, поскольку sendMessage() вызывает его.
  • В функции перенаправления, использование chai.assert , чтобы помочь сделать утверждение о том, каких параметрах функции переадресации должен быть вызван с:
// A fake request object, with req.query.text set to 'input'
const req = { query: {text: 'input'} };
// A fake response object, with a stubbed redirect function which asserts that it is called
// with parameters 303, 'new_ref'.
const res = {
  redirect: (code, url) => {
    assert.equal(code, 303);
    assert.equal(url, 'new_ref');
    done();
  }
};

// Invoke addMessage with our fake request and response objects. This will cause the
// assertions in the response object to be evaluated.
myFunctions.addMessage(req, res);

Очистка теста

В самом конце вашего тестового кода вызовите функцию очистки. Эти переменные среды сбросов , что набор SDK , когда он был инициализирован, и удаляет Firebase приложения , которые могут быть созданы , если вы использовали SDK для создания реального времени базы данных DataSnapshot или Firestore DocumentSnapshot .

test.cleanup();

Просмотрите полные примеры и узнайте больше

Вы можете просмотреть полные примеры в репозитории Firebase GitHub.

Чтобы узнать больше, обратитесь к ссылке API для firebase-functions-test .