如何在 Typescript 中对私有方法进行单元测试

How to unit test private methods in Typescript

当我尝试在 Class 中对 私有方法进行单元测试时出现错误,因为 私有方法只能在 [=34] 中访问=]。在这里,我为我的 class 和 mocha 测试添加了示例片段。请为我提供实现私有方法单元测试的解决方案。

Class 姓名:Notification.ts

class Notification {
 constructor() {}
 public validateTempalte() {
  return true;
 }

 private replacePlaceholder() {
  return true;
 }
}

单元测试:

import {Notification} from 'Notification';
import * as chai from "chai";

describe("Notification", function(){

  describe('#validateTempalte - Validate template', function() {
      it('it should return success', function() {
        const result = new Notification()
        chai.expect(result.validateTempalte()).to.be.equal(true);
      });
    });
  describe('#replacePlaceholder - Replace Placeholder', function() {
      it('it should return success', function() {
        const result = new Notification()
        // As expected getting error "Private is only accessible within class"
        chai.expect(result.replacePlaceholder()).to.be.equal(true);
      });
    });
});

作为解决方法,目前,我正在将函数 replacePlaceholder 的访问说明符更改为 public。但我认为这不是一种有效的方法。

由于在 class 之外无法访问私有方法,您可以使用另一个 public 方法,该方法在 Notification class 中调用 replace Placeholder() 然后测试 public方法。

技术上,在当前版本的 TypeScript 中,私有方法仅 compile-time 检查为私有 - 因此您可以调用它们。

class Example {
    public publicMethod() {
        return 'public';
    }

    private privateMethod() {
        return 'private';
    }
}

const example = new Example();

console.log(example.publicMethod()); // 'public'
console.log(example.privateMethod()); // 'private'

我提到这个只是因为你问了怎么做,而这就是你可以做的。

正确答案

但是,该私有方法必须由其他方法调用...否则根本不会被调用。如果您测试其他方法的行为,您将在使用它的上下文中覆盖私有方法。

如果您专门测试私有方法,您的测试将与实现细节紧密耦合(即,如果重构实现,则不需要更改好的测试)。

免责声明

如果您仍然在私有方法级别进行测试,编译器 将来可能会 更改并使测试失败(即如果编译器使方法 "properly" private,或者如果 ECMAScript 的未来版本添加了可见性关键字等)。

一种省略 Typescript 检查的可能解决方案是动态访问 属性(不知道它是否好)。

myClass['privateProp'] 或方法:myClass['privateMethod']()

在我的例子中,我使用对象的原型来访问私有方法。很好用,TS不骂人

例如:

class Example {
    private privateMethod() {}
}

describe() {
    it('test', () => {
        const example = new Example();
        const exampleProto = Object.getPrototypeOf(example);

        exampleProto.privateMethod();
    })
}

如果您使用静态方法,则使用 exampleProto.<strong>constructor</strong>.privateMethod();.

在 HolgerJeromin 的评论中,comment issue 有一个仍然使用 属性 语法的简洁解决方案。

解决方案是将您的对象 / class 类型转换为 any

示例:

(<any>myClass).privateMethod();
const value = (<any>myClass).privateValue;
(myClass as any).privateMethod();
const value = (myClass as any).privateValue;

此方法满足编译器以及VSCode语法高亮。

这是我在 issue that talks about this

中的一些笔记
  • 通过字符串访问更常见,尽管我不明白为什么它可能更安全。
  • 这些功能是有意设计的,因此它们的帮助大于阻碍。
  • 可能有一种方法可以禁用此类功能,这样人们就不会将此代码复制并粘贴到生产环境中。 "noImplicitAny": true, 可能 有助于 tsconfig.json

将私有函数抽出到separate/stand单独的函数中,但不要导出到外部。

这在语义上是正确的,因为毕竟 — 私有函数是私有的,不应被任何人访问 除了 class 本身。

我的主观解决方案:您可以定义一个新的testing-only接口,通过添加私有方法作为(隐式public)接口方法.然后,将实例化对象转换为这个新的测试类型。这满足 tscVS code 类型检查。你的例子和我的解决方案:

interface INotification {
  validateTemplate(): boolean,
}

class Notification implements INotification {
  constructor() {}

  public validateTemplate() {
    return true;
  }

  private replacePlaceholder() {
    return true;
  }
}

测试:

import {Notification} from 'Notification';
import * as chai from "chai";

interface INotificationTest extends INotification {
  replacePlaceholder(): boolean;
}

describe("Notification", function(){

  describe('#validateTemplate - Validate template', function() {
    it('it should return success', function() {
      const result = new Notification() as INotificationTest;
      chai.expect(result.validateTemplate()).to.be.equal(true);
    });
  });
  describe('#replacePlaceholder - Replace Placeholder', function() {
    it('it should return success', function() {
      const result = new Notification() as INotificationTest;
      // Works!
      chai.expect(result.replacePlaceholder()).to.be.equal(true);
    });
  });
});

优点:

  • tscvs code不抱怨
  • IntelliSense(或任何其他自动完成)有效
  • 简单(主观)
  • 如果您不想定义原始接口 (INotification),您可以完全定义测试接口 (INotificationTest) 而不是以相同的方式扩展和转换它。

缺点:

  • 添加了样板文件
  • 需要更新和同步两个界面
  • 显式转换为非原始类型可能会引入错误。

我让你来决定这是否值得。就我而言,积极因素超过消极因素。我已经用 jest 对此进行了测试,但我认为 mocha.js 在这里没有什么不同。

编辑:但一般来说我会同意 回答