Kiểm tra đơn vị chức năng đám mây

Trang này mô tả các phương pháp và công cụ tốt nhất để viết các bài kiểm tra đơn vị cho các chức năng của bạn, chẳng hạn như các bài kiểm tra sẽ là một phần của hệ thống Tích hợp Liên tục (CI). Để giúp thử nghiệm dễ dàng hơn, Firebase cung cấp SDK thử nghiệm Firebase cho các Chức năng đám mây. Nó được phân phối trên npm dưới dạng firebase-functions-test và là SDK thử nghiệm đồng hành với firebase-functions . SDK thử nghiệm Firebase cho các chức năng của đám mây:

  • Đảm nhiệm việc thiết lập và chia nhỏ phù hợp cho các thử nghiệm của bạn, chẳng hạn như đặt và hủy đặt các biến môi trường cần thiết cho firebase-functions .
  • Tạo dữ liệu mẫu và bối cảnh sự kiện để bạn chỉ phải chỉ định các trường có liên quan đến thử nghiệm của mình.

thiết lập thử nghiệm

Cài đặt cả firebase-functions-testMocha , một khung thử nghiệm, bằng cách chạy các lệnh sau trong thư mục chức năng của bạn:

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

Tiếp theo, tạo một thư mục test bên trong thư mục chức năng, tạo một tệp mới bên trong nó cho mã kiểm tra của bạn và đặt tên cho nó là index.test.js .

Cuối cùng, sửa đổi functions/package.json để thêm vào như sau:

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

Khi bạn đã viết các bài kiểm tra, bạn có thể chạy chúng bằng cách chạy npm test bên trong thư mục chức năng của mình.

Đang khởi tạo SDK thử nghiệm Firebase cho các chức năng của đám mây

Có hai cách để sử dụng firebase-functions-test :

  1. Chế độ trực tuyến (được khuyến nghị): Viết các thử nghiệm tương tác với dự án Firebase dành riêng cho thử nghiệm để cơ sở dữ liệu ghi, người dùng tạo, v.v. sẽ thực sự xảy ra và mã thử nghiệm của bạn có thể kiểm tra kết quả. Điều này cũng có nghĩa là các SDK Google khác được sử dụng trong chức năng của bạn cũng sẽ hoạt động.
  2. Chế độ ngoại tuyến: Viết các bài kiểm tra đơn vị ẩn và ngoại tuyến mà không có tác dụng phụ. Điều này có nghĩa là bất kỳ lệnh gọi phương thức nào tương tác với sản phẩm Firebase (ví dụ: ghi vào cơ sở dữ liệu hoặc tạo người dùng) đều cần phải được khai thác. Nói chung, bạn không nên sử dụng chế độ ngoại tuyến nếu bạn có các chức năng Cơ sở dữ liệu thời gian thực hoặc Cloud Firestore, vì nó làm tăng đáng kể độ phức tạp của mã kiểm tra của bạn.

Khởi tạo SDK ở chế độ trực tuyến (được khuyến nghị)

Nếu bạn muốn viết các bài kiểm tra tương tác với một dự án thử nghiệm, bạn cần cung cấp các giá trị cấu hình dự án cần thiết để khởi tạo ứng dụng thông qua firebase-admin và đường dẫn đến tệp khóa tài khoản dịch vụ.

Để nhận các giá trị cấu hình của dự án Firebase của bạn:

  1. Mở cài đặt dự án của bạn trong bảng điều khiển Firebase .
  2. Trong Ứng dụng của bạn, hãy chọn ứng dụng mong muốn.
  3. Trong ngăn bên phải, chọn tùy chọn tải xuống tệp cấu hình cho ứng dụng Apple và Android.

    Đối với ứng dụng web, hãy chọn Cấu hình để hiển thị các giá trị cấu hình.

Để tạo một tệp chính:

  1. Mở ngăn Tài khoản dịch vụ của Google Cloud Console.
  2. Chọn tài khoản dịch vụ mặc định của App Engine và sử dụng menu tùy chọn ở bên phải để chọn Tạo khóa .
  3. Khi được nhắc, hãy chọn JSON cho loại khóa và nhấp vào Tạo .

Sau khi lưu tệp chính, hãy khởi tạo 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');

Khởi tạo SDK ở chế độ ngoại tuyến

Nếu bạn muốn viết các bài kiểm tra ngoại tuyến hoàn toàn, bạn có thể khởi tạo SDK mà không cần bất kỳ tham số nào:

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

Mocking giá trị cấu hình

Nếu bạn sử dụng functions.config() trong mã chức năng của mình, bạn có thể mô phỏng các giá trị cấu hình. Ví dụ: nếu functions/index.js chứa mã sau:

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

Sau đó, bạn có thể mô phỏng giá trị bên trong tệp thử nghiệm của mình như sau:

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

Nhập chức năng của bạn

Để nhập chức năng của bạn, hãy sử dụng require nhập tệp chức năng chính của bạn dưới dạng mô-đun. Đảm bảo chỉ thực hiện việc này sau khi khởi tạo firebase-functions-test và mô phỏng các giá trị cấu hình.

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

Nếu bạn đã khởi tạo firebase-functions-testchế độ ngoại tuyến và bạn có mã hàm admin.initializeApp() , thì bạn cần khai thác nó trước khi nhập các hàm của mình:

// 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');

Kiểm tra các chức năng nền (không phải HTTP)

Quá trình kiểm tra các chức năng không phải HTTP bao gồm các bước sau:

  1. Gói chức năng bạn muốn kiểm tra bằng phương thức test.wrap
  2. Xây dựng dữ liệu thử nghiệm
  3. Gọi hàm bọc với dữ liệu thử nghiệm bạn đã tạo và bất kỳ trường ngữ cảnh sự kiện nào bạn muốn chỉ định.
  4. Đưa ra khẳng định về hành vi.

Trước tiên, hãy bọc chức năng bạn muốn kiểm tra. Giả sử bạn có một hàm trong functions/index.js được gọi là makeUppercase mà bạn muốn kiểm tra. Viết phần sau vào functions/test/index.test.js

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

wrapped là một chức năng gọi makeUppercase khi nó được gọi. wrapped lấy 2 tham số:

  1. data (bắt buộc): dữ liệu cần gửi tới makeUppercase . Điều này tương ứng trực tiếp với tham số đầu tiên được gửi đến trình xử lý hàm mà bạn đã viết. firebase-functions-test cung cấp các phương thức để xây dựng dữ liệu tùy chỉnh hoặc dữ liệu mẫu.
  2. eventContextOptions (tùy chọn): các trường của ngữ cảnh sự kiện mà bạn muốn chỉ định. Bối cảnh sự kiện là tham số thứ hai được gửi đến trình xử lý hàm mà bạn đã viết. Nếu bạn không bao gồm tham số eventContextOptions khi gọi wrapped , ngữ cảnh sự kiện vẫn được tạo với các trường hợp lý. Bạn có thể ghi đè một số trường đã tạo bằng cách chỉ định chúng tại đây. Lưu ý rằng bạn chỉ phải bao gồm các trường mà bạn muốn ghi đè. Bất kỳ trường nào bạn không ghi đè đều được tạo.
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
});

Xây dựng dữ liệu thử nghiệm

Tham số đầu tiên của hàm được bao bọc là dữ liệu thử nghiệm để gọi hàm bên dưới. Có một số cách để xây dựng dữ liệu thử nghiệm.

Sử dụng dữ liệu tùy chỉnh

firebase-functions-test có một số chức năng để xây dựng dữ liệu cần thiết để kiểm tra các chức năng của bạn. Ví dụ: sử dụng test.firestore.makeDocumentSnapshot để tạo Firestore DocumentSnapshot . Đối số đầu tiên là dữ liệu và đối số thứ hai là đường dẫn tham chiếu đầy đủ và có một đối số thứ ba tùy chọn cho các thuộc tính khác của ảnh chụp nhanh mà bạn có thể chỉ định.

// 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);

Nếu bạn đang kiểm tra chức năng onUpdate hoặc onWrite , bạn sẽ cần tạo hai ảnh chụp nhanh: một cho trạng thái trước và một cho trạng thái sau. Sau đó, bạn có thể sử dụng phương thức makeChange để tạo đối tượng Change với các ảnh chụp nhanh này.

// 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);

Xem tham chiếu API để biết các chức năng tương tự cho tất cả các loại dữ liệu khác.

Sử dụng dữ liệu ví dụ

Nếu bạn không cần tùy chỉnh dữ liệu được sử dụng trong các thử nghiệm của mình, thì firebase-functions-test cung cấp các phương pháp tạo dữ liệu mẫu cho từng loại chức năng.

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

Xem tài liệu tham khảo API để biết các phương pháp lấy dữ liệu mẫu cho mọi loại chức năng.

Sử dụng dữ liệu gốc (đối với chế độ ngoại tuyến)

Nếu bạn đã khởi tạo SDK ở chế độ ngoại tuyến và đang thử nghiệm chức năng Cơ sở dữ liệu thời gian thực hoặc Cloud Firestore, thì bạn nên sử dụng một đối tượng đơn giản có sơ khai thay vì tạo một DocumentSnapshot hoặc DataSnapshot thực tế.

Giả sử bạn đang viết một bài kiểm tra đơn vị cho chức năng sau:

// 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);
    });

Bên trong chức năng, snap được sử dụng hai lần:

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

Trong mã kiểm tra, hãy tạo một đối tượng đơn giản trong đó cả hai đường dẫn mã này sẽ hoạt động và sử dụng Sinon để khai thác các phương thức.

// 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);

khẳng định

Sau khi khởi tạo SDK, gói chức năng và xây dựng dữ liệu, bạn có thể gọi các chức năng được gói với dữ liệu đã tạo và đưa ra xác nhận về hành vi. Bạn có thể sử dụng thư viện như Chai để đưa ra các xác nhận này.

Xác nhận trong chế độ trực tuyến

Nếu bạn đã khởi tạo SDK thử nghiệm Firebase cho Chức năng đám mây ở chế độ trực tuyến , thì bạn có thể khẳng định rằng các hành động mong muốn (chẳng hạn như ghi cơ sở dữ liệu) đã diễn ra bằng cách sử dụng SDK firebase-admin .

Ví dụ dưới đây khẳng định rằng 'INPUT' đã được ghi vào cơ sở dữ liệu của dự án thử nghiệm.

// 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');
  });
});

Đưa ra xác nhận ở chế độ ngoại tuyến

Bạn có thể đưa ra các xác nhận về giá trị trả về dự kiến ​​của hàm:

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);

Bạn cũng có thể sử dụng gián điệp Sinon để khẳng định rằng một số phương thức nhất định đã được gọi và với các tham số mà bạn mong đợi.

Kiểm tra chức năng HTTP

Để kiểm tra các chức năng HTTP onCall, hãy sử dụng phương pháp tương tự như kiểm tra các chức năng nền .

Nếu bạn đang thử nghiệm các hàm HTTP onRequest, bạn nên sử dụng firebase-functions-test nếu:

  • Bạn sử dụng functions.config()
  • Hàm của bạn tương tác với dự án Firebase hoặc các API khác của Google và bạn muốn sử dụng dự án Firebase thực cùng thông tin xác thực của dự án đó cho các thử nghiệm của mình.

Hàm HTTP onRequest nhận hai tham số: đối tượng yêu cầu và đối tượng phản hồi. Đây là cách bạn có thể kiểm tra hàm ví dụ addMessage() :

  • Ghi đè chức năng chuyển hướng trong đối tượng phản hồi, vì sendMessage() gọi nó.
  • Trong chức năng chuyển hướng, hãy sử dụng chai.assert để giúp đưa ra các xác nhận về các tham số mà chức năng chuyển hướng sẽ được gọi với:
// 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);

Dọn dẹp kiểm tra

Ở cuối mã kiểm tra của bạn, hãy gọi chức năng dọn dẹp. Thao tác này sẽ hủy đặt các biến môi trường mà SDK đã đặt khi được khởi chạy và xóa các ứng dụng Firebase có thể đã được tạo nếu bạn đã sử dụng SDK để tạo cơ sở dữ liệu thời gian thực DataSnapshot hoặc Firestore DocumentSnapshot .

test.cleanup();

Xem lại các ví dụ hoàn chỉnh và tìm hiểu thêm

Bạn có thể xem lại các ví dụ hoàn chỉnh trên kho lưu trữ Firebase GitHub.

Để tìm hiểu thêm, hãy tham khảo tham chiếu API cho firebase-functions-test .