You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular单元测试:用Jest Mock自定义Dialog及解决未定义报错

Angular Jest测试:Mock Dialog组件解决openModalDialog未定义错误

问题描述

组件方法showMessagesList会读取localStorage中的isEditMode值,当值为'true'时调用modalDialog.openModalDialog()打开弹窗。但在单元测试中:

  • 第一个测试用例设置isEditMode='true'并调用方法后,触发异常Cannot read property 'openModalDialog' of undefined
  • 第二个测试用例因Mock了方法本身未报错,但未真实覆盖分支逻辑

异常栈:

TypeError: Cannot read property 'openModalDialog' of undefined

      49 |     this.isEdit = localStorage.getItem('isEditMode').toString()
      50 |     if (this.isEdit === 'true') {
    > 51 |       this.modalDialog.openModalDialog()
         |                                ^

问题根源

当前TestBed配置中,ModalDialogComponent的provider使用了空对象useValue: {},该对象没有openModalDialog方法,导致代码执行时调用未定义的属性。

解决方案

1. 正确Mock Dialog组件实例

修改TestBed的providers配置,给ModalDialogComponent提供包含mock方法的对象:

providers: [
  { provide: Window, useValue: window },
  // 替换空对象为带mock方法的实例
  { 
    provide: ModalDialogComponent, 
    useValue: { 
      openModalDialog: jest.fn() 
    } 
  },
],

2. 修复第一个测试用例

让测试真实执行方法逻辑,并断言弹窗方法被调用:

it('should call openModalDialog when isEditMode is true', () => {
  localStorage.setItem('isEditMode', 'true')
  // 获取mock的Dialog实例
  const modalDialog = TestBed.inject(ModalDialogComponent);
  component.showMessagesList()
  
  expect(component.isEdit).toBe('true')
  // 断言弹窗方法被触发
  expect(modalDialog.openModalDialog).toHaveBeenCalled()
})

3. 优化第二个测试用例

移除对showMessagesList的Mock,真实测试分支逻辑:

it('should trigger dialog when isEdit is true', () => {
  const modalDialog = TestBed.inject(ModalDialogComponent);
  localStorage.setItem('isEditMode', 'true')
  
  component.showMessagesList()
  
  expect(modalDialog.openModalDialog).toHaveBeenCalled()
})

4. 可选:隔离localStorage影响

为避免测试间的localStorage数据污染,在每个测试前清理:

beforeEach(() => {
  localStorage.clear();
  component.content = mockContent
})

修改后的完整spec.ts代码

import { ComponentFixture, TestBed } from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import {
  ModalDialogComponent,
  ModalDialogModule,
} from 'modal-dialog'

import { ContentModel } from '../../model/content.model'
import * as componentTypes from '../componentTypes'
import { ComposeComponent } from '../compose-message/compose.component'
import { MessageItemsComponent } from '../message-list/message-item.component'
import { SideBarDrawerComponent } from './side-bar-drawer.component'

window.ResizeObserver =
  window.ResizeObserver ||
  jest.fn().mockImplementation(() => ({
    disconnect: jest.fn(),
    observe: jest.fn(),
    unobserve: jest.fn(),
  }))

describe('SideBarDrawerComponent ', () => {
  let component: SideBarDrawerComponent 
  let fixture: ComponentFixture<SideBarDrawerComponent>

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ModalDialogModule],
      declarations: [
        SideBarDrawerComponent,
        MessageItemsComponent ,
        ComposeComponent,
        ModalDialogComponent,
      ],
      providers: [
        { provide: Window, useValue: window },
        { 
          provide: ModalDialogComponent, 
          useValue: { 
            openModalDialog: jest.fn() 
          } 
        },
      ],
    })
      .compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(SideBarDrawerComponent)
        component = fixture.componentInstance
      })
  })

  beforeEach(() => {
    localStorage.clear();
    component.content = mockContent
  })

  it('should call openModalDialog when isEditMode is true', () => {
    localStorage.setItem('isEditMode', 'true')
    const modalDialog = TestBed.inject(ModalDialogComponent);
    component.showMessagesList()
    
    expect(component.isEdit).toBe('true')
    expect(modalDialog.openModalDialog).toHaveBeenCalled()
  })

  it('should trigger dialog when isEdit is true', () => {
    const modalDialog = TestBed.inject(ModalDialogComponent);
    localStorage.setItem('isEditMode', 'true')
    
    component.showMessagesList()
    
    expect(modalDialog.openModalDialog).toHaveBeenCalled()
  })
})

内容的提问来源于stack exchange,提问作者Vijay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 18:54:22