组件中的模拟服务仍然需要 HttpClient 作为提供者

Mocked service in component still demands HttpClient as provider

我有一个使用模拟服务的组件。我是这样创建的。

注意:这个问题我改了名字,在项目中它有一个更具体的名字。

list.component.spec.ts

describe('ListComponent', () => {
  let component: ListComponent;
  let fixture: ComponentFixture<ListComponent>;
  let ITEMS: ListItem[];
  let mockService;

  beforeEach(async(() => {
    ITEMS = require('../../../../../assets/mockdata/items.json');
    mockService = jasmine.createSpyObj(['getItems']);

    TestBed.configureTestingModule({
      imports: [NoopAnimationsModule, RouterTestingModule, SharedModule],
      declarations: [ListComponent, SomePipe, AnotherPipe],
      providers: [
        {
          provide: Service,
          useValue: mockService,
        },
      ],
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TimesheetListComponent);
    component = fixture.componentInstance;
    mockService.getItems.and.returnValue(of(ITEMS));
    fixture.detectChanges();
  });

  describe('regular behavior', () => {
    it('should create', () => {
      expect(component).toBeTruthy();
    });

list.component.ts

@Component({
  selector: 'app-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.scss'],
})
export class ListComponent implements OnInit {
  // left out props;

  constructor(private service: Service, private router: Router, private route: ActivatedRoute) {
    // This forces the page to reload to the default queryparams when no queryparams are given.
    this.router.routeReuseStrategy.shouldReuseRoute = function() {
      return false;
    };
  }

这会产生以下错误:

Error: StaticInjectorError(DynamicTestModule)[Service -> HttpClient]:
          StaticInjectorError(Platform: core)[Service -> HttpClient]:
            NullInjectorError: No provider for HttpClient!

我有一个完全不同的组件 (DetailComponent) 也使用这个 Service 但它没有任何问题。

我不应该提供 HttpClient,因为 list.component.ts 不依赖该组件,服务有这个,我单独测试了那个代码。

我真是莫名其妙,我是不是在看什么东西呢?非常感谢任何帮助。

测试实例化 child 组件的组件时可能会出现此问题,该组件还使用 HttpClient.

注入服务

为了说明,请考虑以下组件:

@Component({
  selector: 'app-parent-component',
  template: `<app-child-component></app-child-component>`
})
export class ParentComponent {
  constructor(private service: FooService) { }
}

@Component({
  selector: 'app-child-component',
})
export class ChildComponent {
  constructor(private service: BarService) { }
}

假设FooServiceBarService都注入了HttpClient.

根据文档,为 ParentComponent 设置的测试可能类似于:

beforeEach(async(() => {
  const mockService = jasmine.createSpyObject(['fooMethod']);
  TestBed.configureTestingModule({
    declarations: [ParentComponent],
    providers: [
      {
        provide: FooService,
        useValue: mockService,
      },
    ],
  }).compileComponents();
}));

但是,当 angular 实例化 ParentComponent 时,它也会实例化 ChildComponent,这需要注入 BarService。由于您没有告诉 TestBed 提供模拟,它提供实际服务,而实际服务又取决于 HttpClient.

有多种解决方案。如果你想测试 ParentComponentChildComponent 的集成,最简单的做法是告诉 TestBed 模拟 child 所需的 BarService。如果你想保持严格的单元测试,你可以提供一个模拟的 child 组件,它不会向 TestBed:

注入 HttpClient 服务
@Component {(
  selector: 'app-child-component'
)}
export class MockChildComponent {
  constructor() { }
}

并按如下方式设置测试:

TestBed.configureTestingModule({
  declarations: [ParentComponent, ChildComponent],
  providers: [
    {
      provide: Service,
      useValue: mockService,
    },
  ],
}).compileComponents();

或者您可以通过告诉 TestBed 使用 CUSTOM_ELEMENTS_SCHEMA 来覆盖组件实例化:

TestBed.configureTestingModule({
  declarations: [ParentComponent],
  providers: [
    {
      provide: Service,
      useValue: mockService,
    },
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();