javascript - Att stubba exporterad funktion med sinon, mocha och swc misslyckas


Jag försöker stubbla funktionen som exporteras från mitt ES-modul.

Jag använde wildcard-import (import * as) för att stubba det, och det fungerar när ts-node används för transpiling (mocha --require ts-node/register */**/*.spec.ts).

Men när swc används, misslyckas det med meddelandet nedan (mocha --require @swc/register */**/*.spec.ts).

TypeError: Descriptor for property validate is non-configurable and non-writable
/* hash.ts */
import * as argon2 from 'argon2'

export async function encrypt(plain) {
  return await argon2.hash(plain)
}

export async function validate(hash, plain) {
  return await argon2.verify(hash, plain)
}
/* service.ts */
import { validate } from './hash'

export async function isValidUser(user: User, password: string) {
  if (!user || !(await validate(user.password, password))) {
    return false
  }
  return true
}
/* service.spec.ts */
import * as hash from './hash'
import { isValidUser } from './service'
import { stub } from 'sinon'

describe('isValidUser', () => {
  stub(hash, 'validate').callsFake(
    async (passwordFormDB, passwordFromUserInput) =>
      passwordFromDB === passwordFromUserInput
  )

  it('...', async () => {
    /* test `isValidUser` function */
  })
})
// .swcrc
{
  "test": ".*.ts$",
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "importMeta": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    },
    "paths": {
      "src/*": ["./src/*"]
    }
  },
  "module": {
    "type": "commonjs",
    "noInterop": true
  }
}

Svar #1

Eftersom ett liknande problem precis dök upp på Sinons buggspårare bestämde jag mig för att dyka ner och se om jag kan lösa det. Det visar sig att det inte är så svårt, men om du vill ha lite detaljer om bakgrunden bör du kolla in problemet. Jag kommer bara att kopiera och klistra in de relevanta delarna här.

Sinon skapar mockar, stubs och spioner. Det är vanliga, normala funktioner, och vi gör ingen magi med runtime, så vad som än behöver göras måste göras med vanlig Javascript i den givna runtime. Sinon anger tydligt vad problemet är:

Beskrivare för egenskapen validate är icke-konfigurerbar och icke-skrivbar

Om den transpilerade koden begränsar alla från att ändra de där exporterna, kan Sinon inte göra någonting på egen hand. Detta problem är inte ett problem med Sinon, utan med dina transpileringsverktyg.

Medan ts-node tydligt exporterar en skrivbar objektbeskrivare, gör SWC det inte. Detta är i linje med hur ES-moduler ska fungera, så SWC verkar visa korrekt beteende här.

Lösning

Eftersom runtime-miljön inte använder ESM, utan CommonJS efter transpilation med SWC, kan du använda någon av de vanliga link seam-metoderna för CommonJS där du i princip avlyssnar modul-inläsning. Sinons startsida listar en sådan metod, som använder Proxiquire. Du kan också använda Rewire, Quibble (från TestDouble) eller andra verktyg som i princip gör samma sak.

describe('isValidUser', () => {
    const mySpy = sinon.spy(async (passwordFormDB, passwordFromUserInput) =>
        passwordFromDB === passwordFromUserInput)
    quibble('./hash', { validate: mySpy });

    it('...', async () => {
        /* test `isValidUser` function */
    })
})

Den länkade frågan visar också hur man ersätter moduler om du inte använder TypeScript, men kör otranspilerade ESM, genom att använda Quibble som modul-laddare. Den förklarar också varför det för närvarande är svårt (inte omöjligt) att mocka moduler när man riktar sig mot ES-moduler ("moduleResolution": "Node16") med TypeScript, eftersom du behöver skriva en delegerande laddare för att hantera upplösning av filnamn innan du överlämnar till Quibble.

Relaterade artiklar