【发布时间】:2013-04-10 18:27:09
【问题描述】:
我正在研究使用 TypeScript 进行 TDD 的可能性。 如果我用 TypeScript 编写测试,是否可以让 import 语句为我的测试类返回模拟? 还是用纯 JavaScript 编写测试并自己处理注入 AMD 的唯一可行方法?
【问题讨论】:
标签: tdd requirejs typescript amd
我正在研究使用 TypeScript 进行 TDD 的可能性。 如果我用 TypeScript 编写测试,是否可以让 import 语句为我的测试类返回模拟? 还是用纯 JavaScript 编写测试并自己处理注入 AMD 的唯一可行方法?
【问题讨论】:
标签: tdd requirejs typescript amd
您可以使用解决方案:
Lightweight dependency injection container for JavaScript/TypeScript
import {autoInjectable, container} from "tsyringe";
class MyService {
move(){
console.log('myService move 123', );
}
}
class MyServiceMock {
move(){
console.log('mock myService move 777', );
}
}
@autoInjectable()
export class ClassA {
constructor(public service?: MyService) {
}
move(){
this.service?.move();
}
}
container.register(MyService, {
useClass: MyServiceMock
});
new ClassA().move();
输出:
模拟 myService 移动 777
【讨论】:
我一直在开发一个名为 Pigly 的 DI 解决方案。给出有关注入和测试的原始问题的示例(诚然不是自动模拟生成-尽管您可以尝试ts-auto-mockas I've done here):
给定:
interface IDb {
set(key: string, value: string);
}
interface IApi {
setName(name: string);
}
class Api implements IApi {
constructor(private db: IDb) {}
setName(name: string){
this.db.set("name", name);
}
}
我们可以绑定类型,
import { Kernel, toSelf, to, toConst } from 'pigly';
import * as sinon from 'sinon';
let spy = sinon.spy();
let kernel = new Kernel();
kernel.bind(toSelf(Api));
kernel.bind<IApi>(to<Api>());
kernel.bind<IDb>(toConst({ set: spy }));
然后解决并测试:
let api = kernel.get<IApi>();
api.setName("John");
console.log(spy.calledWith("name", "John"));
此示例的执行/编译需要一个打字稿转换器 - 将接口符号和构造函数提供程序编译为纯 JavaScript。 There are a few ways to do this。 ts-node + ttypescript 的做法是要有一个 tsconfig.json:
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"plugins": [{
"transform": "@pigly/transformer"
}]
}
}
并用
执行ts-node --compiler ttypescript example-mock.ts
Pigly 的区别在于不需要对您的(或第三方)类进行任何更改,代价是使用 typescript 转换器,或者如果(您不想使用转换器)则使用更详细的绑定.它仍然是实验性的,但我认为它显示出希望。
【讨论】:
您可以试一试:https://www.npmjs.com/package/easy-injectionjs。它是一个通用的依赖注入包。
@EasySingleton 在整个应用程序中创建依赖项的单个实例。它是某种服务的理想选择。
@EasyPrototype 根据需要创建尽可能多的依赖项实例。它是可变依赖项的理想选择。
@EasyFactory 主要用于继承:
你可以使用这个包做任何事情: 简单用法(来自自述文件):
import { Easy, EasyFactory, EasyPrototype, EasySingleton } from 'easy-injectionjs';
@EasyFactory()
abstract class Person {
abstract getName();
abstract setName(v: string);
}
// @EasyObservable()
@EasySingleton()
class Somebody extends Person{
// @Easy()
constructor (private name: string) {
super()
this.name = 'Sal';
}
public getName() {
return this.name;
}
public setName(v: string) {
this.name = v;
}
}
@EasyPrototype()
class Nobody extends Person{
@Easy()
somebody: Person;
constructor () {
super()
}
public getName() {
return this.somebody.getName();
}
public setName(v: string) {
this.somebody.setName(v);
}
}
@EasyPrototype()
class Data {
@Easy()
somebody: Person;
name: string;
change(v: string) {
this.somebody.setName(v);
}
getName(): string {
return this.somebody.getName();
}
}
let n = new Nobody();
console.log(n.getName()) // Prints Sal
n.setName('awesome');
console.log(n.getName()) // Prints awesome
let d = new Data()
console.log(d.getName()) // Prints awesome
d.change('Gelba')
console.log(n.getName()) // Prints Gelba
d.change('kaa')
console.log(n.getName()) // Prints Kaa
即使你想注入节点模块,你也可以这样做:
import * as IExpress from 'express';
import { Easy, EasySingleton } from 'easy-injectionjs';
@EasySingleton()
class Express extends IExpress {}
@EasySingleton()
export class App {
@Easy()
private _express: Express;
}
let app = new App();
console.log(app)
当然,快速服务器的用途不是用于控制台日志记录。这只是为了测试:D。
希望对你有所帮助:D
【讨论】:
对于使用 Angular2 的人,我开发了 Fluency Injection https://www.npmjs.com/package/fluency-injection。文档非常完整,它模仿了 Angular2 的 DI 的行为。
非常感谢您的反馈,希望对您有所帮助:)
【讨论】:
我开发了一个名为 InversifyJS 的 IoC 容器,它具有上下文绑定等高级依赖注入功能。
你需要遵循3个基本步骤来使用它:
注解 API 基于 Angular 2.0:
import { injectable, inject } from "inversify";
@injectable()
class Katana implements IKatana {
public hit() {
return "cut!";
}
}
@injectable()
class Shuriken implements IShuriken {
public throw() {
return "hit!";
}
}
@injectable()
class Ninja implements INinja {
private _katana: IKatana;
private _shuriken: IShuriken;
public constructor(
@inject("IKatana") katana: IKatana,
@inject("IShuriken") shuriken: IShuriken
) {
this._katana = katana;
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
绑定API基于Ninject:
import { Kernel } from "inversify";
import { Ninja } from "./entities/ninja";
import { Katana } from "./entities/katana";
import { Shuriken} from "./entities/shuriken";
var kernel = new Kernel();
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IKatana>("IKatana").to(Katana);
kernel.bind<IShuriken>("IShuriken").to(Shuriken);
export default kernel;
解析API基于Ninject:
import kernel = from "./inversify.config";
var ninja = kernel.get<INinja>("INinja");
expect(ninja.fight()).eql("cut!"); // true
expect(ninja.sneak()).eql("hit!"); // true
最新版本 (2.0.0) 支持许多用例:
您可以通过https://github.com/inversify/InversifyJS了解更多信息
【讨论】:
我在 TypeScript 中使用 infuse.js 进行依赖注入。
/// <reference path="definition/infusejs/infusejs.d.ts"/>
this.injector = new infuse.Injector();
this.injector.mapClass( 'TodoController', TodoController );
this.injector.mapClass( 'TodoView', TodoView );
this.injector.mapClass( 'TodoModel', TodoModel, true ); // 'true' Map as singleton
export class TodoController
{
static inject = ['TodoView', 'TodoModel'];
constructor( todoView:TodoView, todoModel:TodoModel )
{
}
}
它是基于字符串的,而不是基于类型的(因为在 TypeScript 中还不能实现反射)。尽管如此,它在我的应用程序中运行良好。
【讨论】:
试试这个Dependency Injector (Typejector)
GitHub Typejector
使用新的 TypeScript 1.5 可以使用注释方式
例如
@injection
class SingletonClass {
public cat: string = "Kitty";
public dog: string = "Hot";
public say() {
alert(`${this.cat}-Cat and ${this.dog}-Dog`);
}
}
@injection
class SimpleClass {
public say(something: string) {
alert(`You said ${something}?`);
}
}
@resolve
class NeedInjectionsClass {
@inject(SingletonClass)
public helper: SingletonClass;
@inject(SimpleClass)
public simpleHelper: SimpleClass;
constructor() {
this.helper.say();
this.simpleHelper.say("wow");
}
}
class ChildClass extends NeedInjectionsClass {
}
var needInjection = new ChildClass();
对于问题案例: 一些属性应该像下一个例子那样实现伪接口(或抽象类)。
class InterfaceClass {
public cat: string;
public dog: string;
public say() {
}
}
@injection(true, InterfaceClass)
class SingletonClass extends InterfaceClass {
public cat: string = "Kitty";
public dog: string = "Hot";
public say() {
alert(`${this.cat}-Cat and ${this.dog}-Dog`);
}
}
@injection(true, InterfaceClass)
class MockInterfaceClass extends InterfaceClass {
public cat: string = "Kitty";
public dog: string = "Hot";
public say() {
alert(`Mock-${this.cat}-Cat and Mock-${this.dog}-Dog`);
}
}
@injection
class SimpleClass {
public say(something: string) {
alert(`You said ${something}?`);
}
}
@resolve
class NeedInjectionsClass {
@inject(InterfaceClass)
public helper: InterfaceClass;
@inject(SimpleClass)
public simpleHelper: SimpleClass;
constructor() {
this.helper.say();
this.simpleHelper.say("wow");
}
}
class ChildClass extends NeedInjectionsClass {
}
var needInjection = new ChildClass();
注意: 模拟注入应该在源代码之后定义,因为它会重新定义接口的类创建器
【讨论】:
我在 AutoFixtureTS 上工作,它的灵感来自 AutoFixture。 AutoFixtureTS 通过自动化不相关的测试夹具设置使 TypeScript 开发人员更容易进行测试驱动开发,从而使测试开发人员能够专注于每个测试用例的基本要素。
http://ronniehegelund.github.io/AutoFixtureTS/
它仍然只是原型代码,但请检查一下 :-)
/罗尼
【讨论】:
TypeScript 可以很好地与 requirejs 等 AMD 加载器配合使用。如果配置正确,TypeScript 将输出完全符合 AMD 的 javascript。
在测试情况下,您可以配置 requirejs 以注入可测试模块。
【讨论】: