How to mock a dependent class without changing constructor parameters to optional in TypeScript & JEST?

huangapple go评论71阅读模式
英文:

How to mock a dependent class without changing constructor parameters to optional in TypeScript & JEST?

问题

我正在尝试在Java应用程序的模拟实践中模仿一个非常著名的东西,但这次要使用TypeScript和JEST。
假设我有一个名为Controller的类,它依赖于一个名为Service的类。 Controller通过构造函数声明其依赖关系,使Service成为必需的。
我使用一个依赖注入(DI)库(tsyringe)来在运行时解决依赖关系,因此DI容器将负责在需要时创建Service的实例并将其注入Controller中。

为了清晰起见,这是Controller的源代码:

import { scoped, Lifecycle } from "tsyringe";
import { RouteService } from "./RouteService";
import { RouteDTO } from "./view/RouteDTO";

@scoped(Lifecycle.ContainerScoped)
export class RouteController {

    constructor(private routeService: RouteService) {}

    public createRoute(route: RouteDTO): RouteDTO {
        // 用于测试的业务逻辑
        if (isBusinessLogicValid) {
            return this.routeService.saveRoute(route);
        } else {
            throw Error("Invalid business logic");
        }
    }
}

这是Service的源代码:

import { scoped, Lifecycle } from "tsyringe";
import { UserSession } from "../user/UserSession";
import { RouteDTO } from "./view/RouteDTO";

@scoped(Lifecycle.ContainerScoped)
export class RouteService {

    constructor(
        private userSession: UserSession
    ) {}

    public saveRoute(route: RouteDTO): RouteDTO {
        // 业务逻辑和持久化
        return route
    }

}

我试图以一种方式来模拟RouteService类,以便我不需要手动创建它的实例来单元测试RouteController,否则我需要解决所有下游依赖关系(意味着:RouteController依赖于RouteServiceRouteService依赖于UserSessionUserSession依赖于...)。
在Java中使用Mockito,我可以做类似于以下的事情:

RouteService routeServiceMock = mock(RouteService.class); // 这将是目标
// 在routeServiceMock上进行模拟定义
RouteController controller = new RouteController(routeServiceMock);
RouteDTO newDTO = createRouteDTO();
RouteDTO savedDTO = controller.save(newDTO);
assertThat(savedDTO).isEqualsTo(newDTO);
//... 其他断言

我一直在查看Jest文档,但我找不到等效的内容。是否有人知道这样的事情是否可行?如果是的话,我该如何做到这一点?

英文:

I'm trying to imitate something very well-known in mock practice for Java applications, but this time using TypeScript and JEST.
Suppose I have a class Controller who depends on a class Service. The Controller declares its dependency through the constructor, making the Service to be mandatory.
I use a Dependency Injection (DI) library (tsyringe) to resolve the dependencies in runtime, therefore the DI container will take care of creating an instance of the Service and injecting it into the Controller when the time comes.

For clarity, here it goes the source code for the Controller:

import { scoped, Lifecycle } from "tsyringe";
import { RouteService } from "./RouteService";
import { RouteDTO } from "./view/RouteDTO";

@scoped(Lifecycle.ContainerScoped)
export class RouteController {

    constructor(private routeService: RouteService) {}

    public createRoute(route: RouteDTO): RouteDTO {
        // business logic subject for testing
        if (isBusinessLogicValid) {
            return this.routeService.saveRoute(route);
        } else {
            throw Error("Invalid business logic");
        }
    }
}

and here goes the source code for the Service:

import { scoped, Lifecycle } from "tsyringe";
import { UserSession } from "../user/UserSession";
import { RouteDTO } from "./view/RouteDTO";

@scoped(Lifecycle.ContainerScoped)
export class RouteService {

    constructor(
        private userSession: UserSession
    ) {}

    public saveRoute(route: RouteDTO): RouteDTO {
        // business logic and persistence
        return route
    }

}

I'm trying to mock the class RouteService in a way so that I don't need to create an instance of it manually to unit test the RouteController, otherwise, I'll need to resolve all downstream dependencies (meaning: RouteController depends on RouteService, RouteService depends on UserSession, UserSession depends on ...).
In Java using Mockito I'd be able to do something like this:

RouteService routeServiceMock = mock(RouteService.class); // this'd be the goal
// mock definitions on routeServiceMock
RouteController controller = new RouteController(routeServiceMock);
RouteDTO newDTO = createRouteDTO();
RouteDTO savedDTO = controller.save(newDTO);
assertThat(savedDTO).isEqualsTo(newDTO);
//... other assertions

I've been looking at Jest documentation and I couldn't find anything equivalent. Does anybody know if such a thing is feasiable? If yes, how can I do that?

答案1

得分: 0

似乎我已经找到了一个可行的解决方案,但我对解决方案的结构不是很满意。基本上,我必须将它双重转换为as unknown as RouteService

我可以这样做:

describe('Route controller test', () => {

    beforeEach(() => {
        let routeServiceMock: RouteService = {
            saveRoute: jest.fn(async route => route)
        } as unknown as RouteService
        routeController = new RouteController(routeServiceMock);
    })

   // ...
}
英文:

It seems I have found a workable solution for a while, but I'm not very happy with the structure of the solution. Basically, I have to double cast it as unknown as RouteService.

I can do the following:

describe('Route controller test', () => {

    beforeEach(() => {
        let routeServiceMock: RouteService = {
            saveRoute: jest.fn(async route => route)
        } as unknown as RouteService
        routeController = new RouteController(routeServiceMock);
    })

   // ...
}

huangapple
  • 本文由 发表于 2023年1月9日 00:56:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75049681.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定