英文:
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
依赖于RouteService
,RouteService
依赖于UserSession
,UserSession
依赖于...)。
在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);
})
// ...
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论