如何对从包中导入的方法进行存根和间谍?

问题描述 投票:-2回答:1

我是JavaScript和Python开发人员。这是使用jestjs测试框架的单元测试代码段:

index.ts

import dotenv from 'dotenv';

export class OsEnvFetcher {
  constructor() {
    const output = dotenv.config();
    if (output.error) {
      console.log('Error loading .env file');
      process.exit(1);
    }
  }
}

index.test.ts

import { OsEnvFetcher } from './';
import dotenv from 'dotenv';

describe('OsEnvFetcher', () => {
  afterEach(() => {
    jest.restoreAllMocks();
  });
  it('should pass', () => {
    const mOutput = { error: new Error('parsed failure') };
    jest.spyOn(dotenv, 'config').mockReturnValueOnce(mOutput);
    const errorLogSpy = jest.spyOn(console, 'log');
    const exitStub = jest.spyOn(process, 'exit').mockImplementation();
    new OsEnvFetcher();
    expect(dotenv.config).toBeCalledTimes(1);
    expect(errorLogSpy).toBeCalledWith('Error loading .env file');
    expect(exitStub).toBeCalledWith(1);
  });
});

单元测试的结果:

 PASS  stackoverflow/todo/index.test.ts (11.08s)
  OsEnvFetcher
    ✓ should pass (32ms)

  console.log
    Error loading .env file

      at CustomConsole.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |       50 |     100 |     100 |                   
 index.ts |     100 |       50 |     100 |     100 | 6                 
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        12.467s

示例中的测试方法在使用Arrange, Act, Assert模式的js项目中非常常见。由于dotenv.config()方法将执行某些文件系统I / O操作,因此具有副作用。因此,我们将为此进行存根或模拟。这样,我们的单元测试就没有副作用,并且可以在隔离的环境中进行测试。

同样适用于python。我们可以使用unittest.mock模拟对象库执行相同的操作。我对这些单元测试方法非常满意。

现在,我切换到Golang语言,尝试做同样的事情。代码在这里:

osEnvFetcher.go

package util

import (
    "log"
    "os"
    "github.com/joho/godotenv"
)

var godotenvLoad = godotenv.Load

type EnvFetcher interface {
    Getenv(key string) string
}

type osEnvFetcher struct {}

func NewOsEnvFetcher() *osEnvFetcher {
    err := godotenvLoad()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    return &osEnvFetcher{}
}

func (f *osEnvFetcher) Getenv(key string) string {
    return os.Getenv(key)
}

osEnvFetcher_test.go

package util

import (
    "testing"
    "fmt"
)

func TestOsEnvFetcher(t *testing.T) {
    old := godotenvLoad
    defer func() { godotenvLoad = old }()
    godotenvLoad = func() error {
        return 
    }
    osEnvFetcher := NewOsEnvFetcher()
    port := osEnvFetcher.Getenv("PORT")
    fmt.Println(port)
}

测试用例尚未完成。我不确定如何模拟,存根或监视godotenv.Load方法(等效于dotenv.config())和log.Fatal方法?我找到了这个模拟包-mock。但是godotenv程序包没有接口,由功能组成。

我正在寻找jest.mock(moduleName, factory, options)jest.spyOn(object, methodName)之类的方法。或者,例如stubsspiessinonjs之类的方法。或者,类似于spyOnjasmine。这些方法几乎可以涵盖所有测试方案。无论使用DI还是直接导入模块。

我看到了一些方法,但是它们都有自己的问题。

例如https://stackoverflow.com/a/41661462/6463558

我需要保留十种有副作用的方法吗?我需要将软件包的这些方法分配给10个变量,并在运行测试之前十次在测试用例中将它们替换为模拟版本。这是不可扩展的。也许我可以创建一个__mocks__目录,并将所有模拟版本对象放入其中。这样我就可以在所有测试文件中使用它们。

使用依赖注入更好。通过这种方式,我们可以将模拟对象传递给函数/方法。但是某些方案是直接导入软件包并使用软件包的方法(第3或内置标准库)。这也是我的问题。我认为这种情况是不可避免的,并且肯定会出现在代码的特定层上。

我可以使用jestjs轻松处理这种情况,例如

util.js

exports.resolveAddress = function(addr) {
  // ...
  const data = exports.parseJSON(json);
  return data;
}
exports.parseJSON = function(json) {}

main.js

// import util module directly rather than using Dependency Injection
import util from './util';
function main() {
  return util.resolveAddress();
}

main.test.js

import util from './util';
const mJson = 'mocked json data';
jest.spyOn(util, 'parseJSON').mockReturnValueOnce(mJson)
const actual = main()
expect(actual).toBe('mocked json data');

我直接导入util模块和模拟util.parseJSON方法及其返回值。我不确定Go的软件包是否可以做到这一点。目前,这些问题是Arrange问题。

此外,我还需要检查是否确实调用了方法,以确保代码逻辑和分支正确。 (例如,使用.toBeCalledWith()jestjs方法)。这是Assert问题。

提前感谢!

unit-testing go mocking jestjs stub
1个回答
0
投票

这是我基于此answer的解决方案:

osEnvFetcher.go

package util

import (
    "log"
    "os"
    "github.com/joho/godotenv"
)

var godotenvLoad = godotenv.Load
var logFatal = log.Fatal

type EnvFetcher interface {
    Getenv(key string) string
}

type osEnvFetcher struct {}

func NewOsEnvFetcher() *osEnvFetcher {
    err := godotenvLoad()
    if err != nil {
        logFatal("Error loading .env file")
    }
    return &osEnvFetcher{}
}

func (f *osEnvFetcher) Getenv(key string) string {
    return os.Getenv(key)
}

osEnvFetcher_test.go

package util

import (
    "testing"
    "errors"
)


func mockRestore(oGodotenvLoad func(...string) error, oLogFatal func(v ...interface{})) {
    godotenvLoad = oGodotenvLoad
    logFatal = oLogFatal
}

func TestOsEnvFetcher(t *testing.T) {
    // Arrange
    oGodotenvLoad := godotenvLoad
    oLogFatal := logFatal
    defer mockRestore(oGodotenvLoad, oLogFatal)
    var godotenvLoadCalled = false
    godotenvLoad = func(...string) error {
        godotenvLoadCalled = true
        return errors.New("parsed failure")
    }
    var logFatalCalled = false
    var logFatalCalledWith interface{}
    logFatal = func(v ...interface{}) {
        logFatalCalled = true
        logFatalCalledWith = v[0]
    }
    // Act
    NewOsEnvFetcher()
    // Assert
    if !godotenvLoadCalled {
        t.Errorf("godotenv.Load should be called")
    }
    if !logFatalCalled {
        t.Errorf("log.Fatal should be called")
    }
    if logFatalCalledWith != "Error loading .env file" {
        t.Errorf("log.Fatal should be called with: %s", logFatalCalledWith)
    }
}

覆盖测试的结果:

☁  util [master] ⚡  go test -v -coverprofile cover.out
=== RUN   TestOsEnvFetcher
--- PASS: TestOsEnvFetcher (0.00s)
PASS
coverage: 80.0% of statements

coverage html记者:

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.