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

如何为监听Angular router.events的changeMetaTags方法编写Jasmine单元测试

Angular changeMetaTags 方法Jasmine单元测试实现

前置准备

我们只需要对方法依赖的四个服务做模拟即可,不需要引入真实的路由、翻译服务实现,核心是验证逻辑交互是否符合预期:

  • 用Subject模拟Router的events事件流
  • 模拟嵌套层级的ActivatedRoute子路由结构
  • 模拟TranslateService的翻译查询接口和语言切换事件流
  • 用spy监听SeoService的updateTitle调用情况

完整测试代码

import { TestBed } from '@angular/core/testing';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
import { of, Subject } from 'rxjs';
// 替换为你项目中对应的组件、服务路径
import { YourComponent } from './your.component';
import { SeoService } from './seo.service';

class MockSeoService {
  updateTitle = jasmine.createSpy('updateTitle');
}

describe('YourComponent', () => {
  let component: YourComponent;
  let routerEventsSubject: Subject<any>;
  let langChangeSubject: Subject<LangChangeEvent>;
  let activatedRoute: any;
  let translateService: jasmine.SpyObj<TranslateService>;
  let seoService: SeoService;

  beforeEach(() => {
    routerEventsSubject = new Subject();
    langChangeSubject = new Subject();

    TestBed.configureTestingModule({
      providers: [
        YourComponent,
        {
          provide: Router,
          useValue: { events: routerEventsSubject.asObservable() }
        },
        { provide: ActivatedRoute, useValue: {} },
        {
          provide: TranslateService,
          useValue: {
            get: jasmine.createSpy('get').and.returnValue(of('翻译后的标题')),
            onLangChange: langChangeSubject.asObservable()
          }
        },
        { provide: SeoService, useClass: MockSeoService }
      ]
    });

    component = TestBed.inject(YourComponent);
    activatedRoute = TestBed.inject(ActivatedRoute) as any;
    translateService = TestBed.inject(TranslateService) as jasmine.SpyObj<TranslateService>;
    seoService = TestBed.inject(SeoService);
  });

  describe('changeMetaTags', () => {
    it('导航到携带title元数据的最深层路由时,应调用翻译服务并更新SEO标题', () => {
      // 模拟嵌套子路由结构,最深层路由携带title元数据
      activatedRoute.firstChild = {
        firstChild: {
          snapshot: { data: { title: 'PAGE_TITLE' } },
          firstChild: null
        }
      };

      component.changeMetaTags();
      // 触发导航结束事件
      routerEventsSubject.next(new NavigationEnd(1, '/test', '/test'));

      expect(translateService.get).toHaveBeenCalledWith('PAGE_TITLE');
      expect(seoService.updateTitle).toHaveBeenCalledWith('翻译后的标题');
    });

    it('导航到无title元数据的路由时,不应调用SEO标题更新方法', () => {
      activatedRoute.firstChild = {
        snapshot: { data: { otherParam: 'value' } },
        firstChild: null
      };

      component.changeMetaTags();
      routerEventsSubject.next(new NavigationEnd(1, '/test', '/test'));

      expect(translateService.get).not.toHaveBeenCalled();
      expect(seoService.updateTitle).not.toHaveBeenCalled();
    });

    it('导航到无data元数据的路由时,不应调用SEO标题更新方法', () => {
      activatedRoute.firstChild = {
        snapshot: {},
        firstChild: null
      };

      component.changeMetaTags();
      routerEventsSubject.next(new NavigationEnd(1, '/test', '/test'));

      expect(translateService.get).not.toHaveBeenCalled();
      expect(seoService.updateTitle).not.toHaveBeenCalled();
    });

    it('无激活子路由时,不应调用SEO标题更新方法', () => {
      activatedRoute.firstChild = null;

      component.changeMetaTags();
      routerEventsSubject.next(new NavigationEnd(1, '/test', '/test'));

      expect(translateService.get).not.toHaveBeenCalled();
      expect(seoService.updateTitle).not.toHaveBeenCalled();
    });

    it('语言切换时,如果当前路由存在title元数据,应重新获取对应语言的标题并更新', () => {
      // 先触发一次导航,存储携带title的路由元数据
      activatedRoute.firstChild = {
        snapshot: { data: { title: 'PAGE_TITLE' } },
        firstChild: null
      };
      component.changeMetaTags();
      routerEventsSubject.next(new NavigationEnd(1, '/test', '/test'));
      // 重置调用记录,单独验证语言切换逻辑
      translateService.get.calls.reset();
      seoService.updateTitle.calls.reset();
      // 修改翻译返回值,模拟切换语言后的结果
      translateService.get.and.returnValue(of('切换语言后的标题'));

      // 触发语言切换事件
      langChangeSubject.next({ lang: 'zh', translations: {} } as LangChangeEvent);

      expect(translateService.get).toHaveBeenCalledWith('PAGE_TITLE');
      expect(seoService.updateTitle).toHaveBeenCalledWith('切换语言后的标题');
    });

    it('语言切换时,如果当前路由无title元数据,不应调用SEO标题更新方法', () => {
      // 先触发一次导航,存储无title的路由元数据
      activatedRoute.firstChild = {
        snapshot: { data: {} },
        firstChild: null
      };
      component.changeMetaTags();
      routerEventsSubject.next(new NavigationEnd(1, '/test', '/test'));

      // 触发语言切换事件
      langChangeSubject.next({ lang: 'en', translations: {} } as LangChangeEvent);

      expect(translateService.get).not.toHaveBeenCalled();
      expect(seoService.updateTitle).not.toHaveBeenCalled();
    });
  });
});

使用说明

  • 替换代码中YourComponent、SeoService的路径和类名为你项目中的实际值
  • 如果你使用的不是@ngx-translate/core翻译库,对应调整TranslateService的模拟逻辑即可
  • 所有分支逻辑均已覆盖:嵌套路由遍历、有无title元数据、语言切换正反场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 02:06:03