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

Trang này mô tả các phương pháp hay nhất và công cụ để viết 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 việc kiểm tra dễ dàng hơn, Firebase cung cấp SDK kiểm tra 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 đám mây:

  • Đảm nhiệm việc thiết lập và phân tích thích hợp cho các thử nghiệm của bạn, chẳng hạn như cài đặ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 kiểm tra, bằng cách chạy các lệnh sau trong thư mục hàm 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 hàm, 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 nó là index.test.js .

Cuối cùng, sửa đổi functions/package.json để thêm thông tin sau:

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

Khi bạn đã viết xong 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 hàm của mình.

Đang khởi tạo SDK thử nghiệm Firebase cho các chức năng đá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 bài kiểm tra tương tác với dự án Firebase dành riêng cho thử nghiệm để việc ghi cơ sở dữ liệu, người dùng tạo, v.v. thực sự xảy ra và mã kiểm tra 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 các 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ị im lặng 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 loại bỏ. Nói chung, bạn không nên sử dụng chế độ ngoại tuyến nếu có chức năng Cloud Firestore hoặc Cơ sở dữ liệu thời gian thực 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 muốn viết các bài kiểm thử 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, chọn ứng dụng mong muốn.
  3. Trong khung 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, chọn Cấu hình để hiển thị giá trị cấu hình.

Để tạo một tập tin khóa:

  1. Mở ngăn Tài khoản dịch vụ của bảng điều khiển Google Cloud.
  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 Create key .
  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 khóa, 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 muốn viết các bài kiểm thử hoàn toàn ngoại tuyế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')();

Giá trị cấu hình mô phỏng

Nếu bạn sử dụng functions.config() trong mã hàm 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ể giả định 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 hàm của bạn

Để nhập các hàm của bạn, hãy sử dụng require để nhập tệp hàm 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ó admin.initializeApp() trong mã hàm của mình thì bạn cần phải bỏ mã đó 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 hàm 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 được bao bọc với dữ liệu thử nghiệm 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 những khẳng định về hành vi.

Đầu 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 hàm gọi makeUppercase khi nó được gọi. wrapped có 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 tới 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 bối 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 tới 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 bằng các trường hợp lý. Bạn có thể ghi đè một số trường được tạo bằng cách chỉ định chúng ở đâ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 cơ bản. 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ố hàm để xây dựng dữ liệu cần thiết để kiểm tra các hàm 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ó đố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 đang kiểm tra hàm 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 những ả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 tài liệu tham khảo 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 mẫu

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 sẽ cung cấp các phương thức để 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 hàm.

Sử dụng dữ liệu sơ khai (đố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 Cloud Firestore hoặc Cơ sở dữ liệu thời gian thực, bạn nên sử dụng một đối tượng đơn giản có phần sơ khai thay vì tạo DocumentSnapshot hoặc DataSnapshot thực tế.

Giả sử bạn đang viết bài kiểm thử đơn vị cho hàm 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 hàm, 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 báo 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);

Đưa ra khẳng định

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

Đưa ra khẳng định ở chế độ trực tuyến

Nếu bạn đã khởi chạy SDK thử nghiệm Firebase cho Chức năng đám mây ở chế độ trực tuyến , bạn có thể xác nhận 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ề mong đợi 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 để xác nhận rằng một số phương thức nhất định đã được gọi và với các tham số bạn mong đợi.

Kiểm tra các chức năng HTTP

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

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

Kiểm tra dọn dẹp

Ở cuối mã kiểm tra, hãy gọi hàm dọn dẹp. Điều này hủy đặt các biến môi trường mà SDK đã đặt khi khởi tạo 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ụ đầy đủ trên kho lưu trữ Firebase GitHub.

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