在单元测试中模拟Angular Material对话框的afterClosed()方法

22

我正在使用以下函数打开我的 mat-dialog:

accept() {
  let dialogRef = this.dialog.open(AcceptDialogComponent, {
    data: {
      hasAccepted: false
    }
  })
  dialogRef.afterClosed().subscribe(result => {
    console.log(result);
    if (result.hasAccepted === true) {
      this.leadService.acceptLead(this.holdingAccountId, this.lead.id)
        .pipe(
          takeUntil(this.onDestroy$)
        )
        .subscribe(acceptLeadRes => {
            console.log(acceptLeadRes);
            this.leadService.updateLeadAction('accept');
          },
          (err: HttpErrorResponse) => {
            console.log(err);
            this.router.navigate(['/error']);
          });
    }
  });
}

我正在尝试为这个函数编写一个测试,只需触发afterClosed(),以便我可以检查是否调用了我的服务方法,该方法将进行后端调用。

component.spec.ts(在Testbed创建之前)

beforeEach(async (() => {
  TestBed.configureTestingModule({
      declarations: [LeadCardComponent, AcceptDialogComponent],
      imports: [
        requiredTestModules,
        JwtModule.forRoot({
          config: {
            tokenGetter: () => {
              return '';
            }
          }
        })
      ],
      providers: [
        ApplicationInsightsService,
        JwtHelperService,
        // { provide: LeadsService, useValue: leadServiceSpy }
      ],
    }),

    TestBed.overrideModule(BrowserDynamicTestingModule, {
      set: {
        entryComponents: [AcceptDialogComponent]
      }
    });
  TestBed.compileComponents();
}));

组件.spec.ts(测试)

it('Return from AcceptLeadDialog with hasAccepted equals true should call acceptLead endpoint', () => {
  let matDiaglogref = dialog.open(AcceptDialogComponent, {
    data: {
      hasAccepted: false
    }
  });
  spyOn(matDiaglogref, 'afterClosed').and.callThrough().and.returnValue({
    hasAccepted: true
  });
  spyOn(leadService, 'acceptLead').and.callThrough();
  component.acceptLead();
  fixture.detectChanges();
  matDiaglogref.close();
  fixture.detectChanges();

  expect(leadService.acceptLead).toHaveBeenCalled();
});

测试目前失败,显示"Expected spy acceptLead to have been called." 我不太明白如何测试该函数并执行某种模拟MatDialogRef的方式,以便我可以检查我的测试是否通过条件。

非常感谢任何帮助/建议

更新:采用已接受答案中的工作测试

it('Return from AcceptLeadDialog with hasAccepted equals true should call acceptLead endpoint', () => {
  spyOn(component.dialog, 'open')
    .and
    .returnValue({
      afterClosed: () => of({
        hasAccepted: true
      })
    });
  spyOn(leadService, 'acceptLead').and.callThrough();
  component.acceptLead();
  expect(component.dialog).toBeDefined();
  expect(leadService.acceptLead).toHaveBeenCalled();
});

你尝试过模拟 MatDialogopen 方法吗?这样做可以让你返回一个你控制的模拟 MatDialogRef,并且你可以手动触发 afterClosed 可观察对象以发出新值。 - Daniel W Strimpel
我能手动触发afterClosed并测试它的响应逻辑,以便作为执行其他代码的条件吗? - Brian Stanley
你解决了你的问题吗?这是完整的解决方案,因为我也想测试弹出窗口的打开和关闭。 - dna
@dna 已经有一段时间了,但是被接受的答案对我的测试有效。 - Brian Stanley
有没有可能拥有完整的测试,可以正常工作? - dna
已更新并测试通过。希望能有所帮助。 - Brian Stanley
4个回答

41

我按照第一篇帖子@Adithya Sreyaj的方法解决了这个问题,但是添加了下一个变化:

spyOn(component.dialog, 'open')
    .and
    .returnValue({
        afterClosed: () => of(true)
    } as MatDialogRef<typeof component>);

3
这应该是第一位的最佳答案。 - EM-Creations
1
这仅在dialogcomponent的公共成员时才有效,不是吗? - Elias
最佳答案。不要提供MatDialogRef的所有函数和属性,只更改您需要的部分。 - Olrhain
2
我不确定在Jest 28之后这是否有效。我们现在必须使用jest.spyOn,不能再使用.and.returnValue... - cmcevoy

31

您可以通过以下方式测试Angular Material Dialog的afterClosed方法:

  1. import { of } from 'rxjs';
  2. 对对话框进行间谍操作,并返回afterClosed()方法的可观察对象
spyOn(component.dialog, 'open')
     .and
     .returnValue({afterClosed: () => of(true)});

基本上,dialogRef的afterClosed()需要一个Observable。因此,我们对组件的对话框打开方法进行间谍,并使用rxjs中的of运算符返回afterClosed()方法的Observable。

然后,您可以将returnValue中的of(true)替换为您在主组件的对话框中发送到afterClosed()的自己的数据。


7
出现错误了。你能帮我解决一下吗?类型为“{ afterClosed: () => Observable<boolean>; }”的参数无法赋值给类型“MatDialogRef<unknown, unknown>”的参数。 类型“{ afterClosed: () => Observable<boolean>; }”缺少类型“MatDialogRef<unknown, unknown>”中的以下属性:_overlayRef、_containerInstance、id、componentInstance以及其他18个属性。 - Sunil Soni
@SunilSoni,我也遇到了同样的问题。你解决了吗? - Rod Nolan
3
@SunilSoni 嗯,.open 通常应该返回一个 MatDialogRef。在这种情况下,您只返回了该对象的一部分,因此 TypesScript 对此进行了投诉。为了满足 TypeScript,您可以使用以下方式转换值:.and.returnValue({ afterClosed: () => of(data) } as MatDialogRef<unknown>)。但请注意,TypeScript 的投诉是相当合理的。如果在测试中调用 MatDialogRef 的任何其他方法,则会尝试调用 undefined 而失败。 - Elias
@RodNolan请看一下我在这个问题上的另一个评论。 - Elias
1
感谢 @Elias 的帮助。 - Sunil Soni

3

我认为你没有理解单元测试组件的整个重点。根据我的理解:

  1. 您有一个函数accept(),它创建了对this.dialog关闭事件的订阅。
  2. 应编写单元测试以验证逻辑,即订阅正在被创建并且服务正在被调用。
  3. dialogRef设置为全局变量,而不是将其保留为accept()的私有变量。这将有助于更好地测试代码。private变量无法在单元测试期间访问。

所以:

component.ts

accept() {
 this.dialogRef = this.dialog.open(AcceptDialogComponent, {
  data: {
    hasAccepted: false
      }
    })
 this.dialogRef.afterClosed().subscribe(result => {
  console.log(result);
  if (result.hasAccepted === true) {
    this.leadService.acceptLead(this.holdingAccountId, this.lead.id)
    .pipe(
      takeUntil(this.onDestroy$)
    )
    .subscribe (acceptLeadRes => {
      console.log(acceptLeadRes);
      this.leadService.updateLeadAction('accept');
    },
    (err: HttpErrorResponse) => {
      console.log(err);
      this.router.navigate(['/error']);
    });
   }
 });
}

spec.ts

it('should create subscription to Modal closing event and call "acceptLead()" of "leadService" ', () => {
    spyOn(component.dialogRef, 'afterClosed').and.returnValue(
        of({
            hasAccepted: false
        })
    );
    spyOn(component.leadService, 'acceptLead').and.callThrough();
    component.accept();
    expect(component.dialogRef).toBeDefined();
    expect(component.dialogRef.afterClosed).toHaveBeenCalled();
    expect(component.leadService.acceptLead).toHaveBeenCalled();
});



抱歉耽搁了。我无法让您的解决方案运行。我可以将dialogRef设置为组件全局变量,但是我从该组件打开了几个不同的对话框,这样做是否会产生冲突? - Brian Stanley
@BrianStanley:如果您正在打开多个dialogRef,则必须为所有全局创建单独的refs。如果将变量保持为私有,则无法使用jasmine进行测试。 - Shashank Vivek

0

有人使用过对话框,将回调函数传递给onOk方法而不是在afterClosed方法中吗?

例如:

 let modalOptions = {
  uContent: 'are you sure you want to delete',
  uTitle: 'modal title',
  uOnOk: () => {
   callback function here
  }
};
this.modalService.create(modalOptions);

想要使用Jest测试回调函数


如果这是一个问题,请创建一个新的问题,而不要将其作为答案提供。 - Jason Foglia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接