You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何监视并断言未导出方法?Deno测试场景求助

Deno测试:验证prepareData执行及返回值的问题

项目结构

├── Readme.md
├── input.json
├── main.ts
├── services
│   ├── calculateTransactionTotals.js
│   └── validateInput.js
└── tests
    ├── mocks.js
    └── tests.test.js

相关代码

main.js

import { validateInput, readInputFile } from './services/validateInput.js'
import { calculateTransactionTotals } from './services/calculateTransactionTotals.js'

validateInput()

const content = await readInputFile()

calculateTransactionTotals(content)

calculateTransactionTotals.js

import groupby from 'https://esm.sh/lodash.groupby'
import moment from 'https://esm.sh/moment'

export function calculateTransactionTotals(content) {
  groupTransactionsByUserAndCurrency(content) // 省略实现,主要是多次groupby操作
  prepareData(groupTransactionsByUserAndCurrency(content), content)
}

function prepareData(transactionsGroupedByUserIdAndCurrency, content) {
  const result = {}
  // 此处为业务逻辑
  printTable(result)
  return result
}

function printTable(result) {
  console.table(result, ['GBP', 'EUR', 'USD', 'last-activity'])
}

export const _internals = { prepareData }

当前测试代码(无法生效)

import { calculateTransactionTotals, _internals } from '../services/calculateTransactionTotals.js'
import data from './mocks.js'
import { assertSpyCalls, spy } from 'https://deno.land/std@0.158.0/testing/mock.ts'

Deno.test('is the expected result returned', () => {
  const prepareDataSpy = spy(_internals, 'prepareData')
  calculateTransactionTotals(data)
  assertSpyCalls(prepareDataSpy, 1)
})

问题原因

当前spy无法生效,是因为calculateTransactionTotals内部直接调用的是模块内的prepareData函数,而_internals.prepareData只是该函数的一个引用副本——spy拦截的是副本的调用,不会影响模块内部的原函数调用。

解决方案

方案1:重构代码,让依赖可注入

修改calculateTransactionTotals,允许传入自定义的prepareData函数,同时让函数返回处理结果,方便测试断言:

// calculateTransactionTotals.js
export function calculateTransactionTotals(content, prepareFn = prepareData) {
  const groupedData = groupTransactionsByUserAndCurrency(content)
  return prepareFn(groupedData, content) // 返回处理结果
}

测试时传入mock函数,直接验证调用次数和返回值:

// tests.test.js
import { calculateTransactionTotals } from '../services/calculateTransactionTotals.js'
import data from './mocks.js'
import { assertSpyCalls, spy } from 'https://deno.land/std@0.158.0/testing/mock.ts'
import { assertEquals } from 'https://deno.land/std@0.158.0/testing/asserts.ts'

Deno.test('prepareData is called once and returns expected value', () => {
  const mockPrepare = spy((grouped, content) => {
    // 模拟业务逻辑返回预期结果
    return {
      "user_1": { GBP: 300, EUR: 0, USD: 150, "last-activity": "2024-06-01" }
    }
  })

  const result = calculateTransactionTotals(data, mockPrepare)
  
  assertSpyCalls(mockPrepare, 1)
  assertEquals(result, {
    "user_1": { GBP: 300, EUR: 0, USD: 150, "last-activity": "2024-06-01" }
  })
})

方案2:直接测试calculateTransactionTotals的输出结果

如果不需要验证内部函数调用,只需要确保最终聚合结果正确,可以直接测试函数返回值:

// 先修改calculateTransactionTotals,让它返回prepareData的结果
export function calculateTransactionTotals(content) {
  const groupedData = groupTransactionsByUserAndCurrency(content)
  return prepareData(groupedData, content)
}

测试代码:

import { calculateTransactionTotals } from '../services/calculateTransactionTotals.js'
import data from './mocks.js'
import { assertEquals } from 'https://deno.land/std@0.158.0/testing/asserts.ts'

Deno.test('returns correct aggregated transaction totals', () => {
  // 根据mock data定义预期的聚合结果
  const expected = {
    "user_123": {
      GBP: 500,
      EUR: 200,
      USD: 0,
      "last-activity": "2024-05-20"
    },
    "user_456": {
      GBP: 0,
      EUR: 100,
      USD: 300,
      "last-activity": "2024-05-25"
    }
  }

  const result = calculateTransactionTotals(data)
  assertEquals(result, expected)
})

方案3:使用mockModule拦截模块内部函数

如果不想修改业务代码,可以用Deno的mockModule来替换模块内的prepareData函数:

import data from './mocks.js'
import { assertSpyCalls, spy, mockModule } from 'https://deno.land/std@0.158.0/testing/mock.ts'
import { assertEquals } from 'https://deno.land/std@0.158.0/testing/asserts.ts'

Deno.test('prepareData is called and returns expected value', async () => {
  const mockPrepareData = spy(() => ({ test_result: 'valid' }))

  // 拦截模块,替换内部的prepareData
  const { calculateTransactionTotals: mockedCalc } = await mockModule(
    '../services/calculateTransactionTotals.js',
    (mod) => {
      mod.prepareData = mockPrepareData
    }
  )

  const result = mockedCalc(data)
  
  assertSpyCalls(mockPrepareData, 1)
  assertEquals(result, { test_result: 'valid' })
})

内容的提问来源于stack exchange,提问作者George Katsanos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 21:20:28