Search code examples
firebaseunit-testinggoogle-cloud-functionsvitest

Testing Firebase callable functions with vitest


Hi wrote this function that takes an url and send back metadata of the page scraped with metascraper

import * as functions from "firebase-functions";
import metascraper from "metascraper";
import metascraperTitle from "metascraper-title";
import metascraperDescription from "metascraper-description";

export const getMetadata = functions.https.onCall(async (data, context) => {
  try {
    const url = data.url;
    const res = await fetch(url);
    const html = await res.text();
    const metadata = await metascraper([
      metascraperTitle(),
      metascraperDescription(),
    ])({html, url});
    return {
      title: metadata.title,
      description: metadata.description,
    };
  } catch (error) {
    functions.logger.error(error);
  }
  return false;
});

I wrote this test to test this callable function:

import {describe , it, expect} from "vitest"
import { getMetadata } from "./getMetadata";

describe('getMetadata', () => {
  it('should return metadata for a given URL', async () => {
    const data= { 'url': 'https://example.com' };
    const context = { };
   
    const result = await getMetadata(data, context);
    
    expect(result).toEqual({ title: 'Example Domain', description: null });
  });
});

Here's the error I get

 FAIL  src/getMetadata.test.ts > getMetadata > should return metadata for a given URL
TypeError: res.on is not a function
 ❯ node_modules/firebase-functions/lib/common/providers/https.js:392:17
 ❯ Module.<anonymous> node_modules/firebase-functions/lib/common/providers/https.js:391:16
 ❯ src/getMetadata.test.ts:10:26
      8|     const context = { };
      9|    
     10|     const result = await getMetadata(data, context);
       |                          ^
     11|     
     12|     expect(result).toEqual({ title: 'Example Domain', description: null });

Can someone please help I don't understand the error, how to fix it?


Solution

  • Use the firebase-functions-test package to test your onCall functions. The short explanation is that you will use that package to wrap your original function and then you call the wrapped function instead.

    Here's a modified example of your function:

    import * as functions from "firebase-functions";
    
    export const getMetadata = functions.https.onCall(async (data, context) => {
      try {
        const url = data.url;
        const res = await fetch(url);
        const html = await res.text();
        return {
          url,
          html
        };
      } catch (error) {
        functions.logger.error(error);
      }
      return false;
    });
    

    And here's a test that will pass successfully:

    import { describe , it, expect } from "vitest";
    import { getMetadata } from "./getMetadata";
    import firebaseFunctionsTest from "firebase-functions-test";
    
    // Extracting `wrap` out of the lazy-loaded features
    const { wrap } = firebaseFunctionsTest();
    
    describe('getMetadata', () => {
      it('should return metadata for a given URL', async () => {
        // Wrap your function here and then call your wrapped function
        const wrappedFirebaseFunction = wrap(getMetadata);
        const data = { url: 'https://example.com' };
        const result = await wrappedFirebaseFunction(data);
    
        expect(result.url).toEqual('https://example.com');
      });
    });
    

    The output:

    vitest run index.test.js
    
     RUN  v0.31.0 /Users/foo/temp
    
     ✓ index.test.js (1)
    
     Test Files  1 passed (1)
          Tests  1 passed (1)
       Start at  10:36:23
       Duration  703ms (transform 21ms, setup 0ms, collect 223ms, tests 67ms, environment 0ms, prepare 64ms)
    

    You can read more about unit testing functions at Unit testing of Cloud Functions.