Эта страница была переведа с помощью Cloud Translation API.
Switch to English

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

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

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

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

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

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.

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

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

  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 в 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 вызывает makeUppercase . wrapped принимает 2 параметра:

  1. data (обязательно): данные для отправки в 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 или Realtime Database, вам следует использовать простой объект с заглушками вместо создания фактического 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();
      console.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 для облачных функций в онлайн-режиме , вы можете утверждать, что желаемые действия (например, запись в базу данных) были выполнены, с помощью SDK firebase-admin .

В приведенном ниже примере утверждается, что «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);

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

Тестирование функций 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 .