Angular/Jasmine 未调用服务方法

Angular/Jasmine service method was not called

我正在尝试做一个简单的测试,当用户点击按钮时它应该从服务中调用方法。 但我仍然只是没有调用那个方法。 Component.ts

@Component({
    ...
    providers: [MyService]
})
export class MyComponent implements OnInit, OnDestroy {
    constructor(public myService: myService) { }
}

Component.html

<button id="myId"
        (click)="myService.myMethodX()">
</button>

MyService.ts

    @Injectable()
    
    export class MyService {
    
        constructor() { }
        myMethodX(){
           ...
        }
}

而在 jasmine 中我是这样测试的。

const mySpyObj = jasmine.createSpyObj('MyService', ['myMethodX']);

it('...', () => {
    // Given
    const button = ...

    // When
    button.click();
    fixture.detectChanges();

    // Then
    expect(mySpyObj.myMethodX).toHaveBeenCalled();
});

但是它说它不叫我做错了什么?

从您的组件调用 myMethodX 的方法不是服务的方法。在该方法内部调用服务方法(我假设),例如 myService.calculate().

你应该监视和测试的是服务中的那个方法。你可以这样做:

// Arange
spyOn(myService, 'calculate');

// Act
button.click();

// Assert
expect(myService.calculate).toHaveBeenCalled();

第二种方法是测试组件的方法是否被调用:

// Arange
spyOn(component, 'myMethodX');

// Act
button.click();

// Assert
expect(component.myMethodX).toHaveBeenCalled();

您必须将您的模拟注入到测试场景中,现在您正在使用真实的实现。通常你会通过 TestBed#configureTestModule 上的 providers 来完成它,但是你的服务是“组件”范围而不是单例,因此你也必须覆盖它。

在这里你有工作 example.Pay 注意评论,尤其是 overrideComponent 电话。它具有至关重要的影响,因为如果没有它,就会使用真正的实现而不是我们的模拟。

// your service
    @Injectable()
    class FooBar {
      foo() {
        return 'bar';
      }
    }
    
//your component
    @Component({
      template: `
        <button (click)="fooBar.foo()"></button>
      `,
      providers: [FooBar] // FooBar is in scope of component, not singleton, therfore....
    })
    class FooBarComponent {
      constructor(public fooBar: FooBar) {
      }
    }
    
    describe('Foobar', async () => {
      let fooMock: SpyObj<FooBar>;
      beforeEach(async () => {
        fooMock = createSpyObj<FooBar>(['foo']);
        return TestBed.configureTestingModule({
          declarations: [FooBarComponent],
        }).overrideComponent(FooBarComponent, {
          set: { // ..... threfore we are overriding component scope provider - now our mock will be provided instead of real implementation
            providers: [
              {provide: FooBar, useValue: fooMock} // here you actually start using your mock inside component
            ]
          }
        }).compileComponents();
      });

    //passes without a problems
          it('calls foo() on click', () => {
            const fixture = TestBed.createComponent(FooBarComponent);
            fixture.detectChanges();
            fixture.debugElement.query(By.css('button')).nativeElement.click();
            expect(fooMock.foo).toHaveBeenCalled();
          });
        });