测试 Angular 扩展抽象 class 的组件

Testing Angular component extending an abstract class

我对许多组件进行了此设置:

@Directive()
export abstract class BaseComponent implements OnInit {

   @Select(PortalState.portal) public portalState: Observable<PortalModel>;
   public portal: PortalModel;
   protected ngUnsubscribe: Subject<void> = new Subject();

   constructor(
     protected someService: SomeService,
     protected route: ActivatedRoute
     ){
   }

   public ngOnInit(): void {
     this.route.params
     .pipe(
        filter(res => !!res),
        tap(res => this.id = res['id']),
        switchMap(() => this.portalState),
        takeUntil(this.ngUnsubscribe)
     )
     .subscribe(res => {
        this.portal = res;
        this.afterInit();
     });
   }

   public ngOnDestroy(): void {
       this.ngUnsubscribe.next();
       this.ngUnsubscribe.complete();
   }

   public abstract afterInit(): void
}

然后在每个扩展这个基础的组件中 class:

@Component({...})
export class SomeComponent extends BaseComponent {

   public specificVar: any;

   constructor(
     protected someService: SomeService,
     protected route: ActivatedRoute
   ){
      super(someService, route);
   }

   public afterInit(): void {
     /** do component specific stuff */
     this.specificVar = 'something';
   }

}

现在它工作正常,但是在测试时,抽象组件的 ngOnInit 似乎根本没有被调用渲染(在这个例子中)this.specific 变量未定义。

spec 文件看起来很传统

let store: Store; /** Ngxs store */
const mockPortalModel: PortalModel = {id: 1};

beforeEach(async () => {
  TestBed.configureTestingModule(/** providers and usual setup */);
  store = TestBed.inject(Store);
  store.reset({
    portalState: mockPortalModel
  });
  jest.spyOn(store, 'dispatch');
  jest.spyOn(store, 'select');
});

beforeEach(() => {
   fixture = TestBed.createComponent(SomeComponent);
   component = fixture.componentInstance;
   fixture.detectChanges();
});

知道为什么 ngOnInit 和随后的 afterInit 没有被调用吗?

您必须在 SomeComponent 中编写以下函数,以便它 运行 您的 BaseComponentngOnInit 方法在 SomeComponentngOnInit方法。

ngOnInit() {
  super.ngOnInit();
}

问题出在必须模拟的 this.route.params 中,因此订阅可能会触发。所以模拟激活的路由已经解决了这个问题:

const mockActivatedRoute = {
  snapshot: {}, // needs to be there to avoid _lastPathIndex error
  params: of({id: 123})
}

/** ...  */
providers: [
  {provide: ActivatedRoute, useValue: mockActivatedRoute }
]

已确保订阅通过(请参阅 filter(res => !!res) - 它就在那里停止)。