【问题标题】:How to test simple Angular service with dependencies如何使用依赖项测试简单的 Angular 服务
【发布时间】:2021-02-01 16:11:21
【问题描述】:

我想了解如何使用在 Angular 中创建的依赖项来测试服务。该服务只有一种方法可以打开 Angular Material Snackbar 并且不返回任何内容。取决于条件,有两个选项可以显示 - 成功或错误。该服务没有公共属性,一切都在它包含的唯一方法中完成。该服务也有一些依赖关系。伪代码如下:

export class SnackbarService {

  constructor(private snackBar: MatSnackBar, private status: Status) { }

  openSnackBar(arg: string | Error):void {
    let prop1: string;
    let prop2: number;
    let prop3: string;

    if (arg instanceof Error) {
      prop1 = this.status.default;
      prop2 = 5000;
      prop3 = 'error';
    } else {
      prop1 = 'another status';
      prop2 = 6000;
      prop3 = 'success';
    }

    this.snackBar.open(prop1, 'x', {
      prop2,
      horizontalPosition: 'center',
      verticalPosition: 'top',
      prop3
    });

}

现在是测试。我在想如果以另一种方式编写上述服务可能会更容易测试 - 将公共属性作为参数传递给两个方法而不是一个方法 - 一个解决 if 语句,另一个实际打开 Snackbar .但是,假设代码如下所示。当方法没有返回任何东西并且没有公共属性时,可以实际测试什么?

describe('SnackbarService', () => {
  let service: SnackbarService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        MatSnackBarModule
      ],
      providers: [
        SnackbarService
      ]
    });
    service = TestBed.inject(SnackbarService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

我知道应该将依赖项导入到测试中,但是除了创建服务本身之外,我不知道如何测试其他任何东西。感谢任何建设性的提示!

【问题讨论】:

    标签: angular typescript unit-testing testing jasmine


    【解决方案1】:

    由于MatSnackBar 被依赖注入,您可以模拟它,窥探它的open 方法,并验证传递给它的参数。

    所以您的测试设置如下所示:

    const mockMatSnackBar = {
      open: () => {}
    };
    
    const mockStatus = {
      default: ''
    };
    
    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [
        ],
        providers: [
          SnackbarService,
          { provide: MatSnackBar, useValue: mockMatSnackBar }, // <--- use mock
          { provide: Status, useValue: mockStatus } // <--- use mock
        ]
      });
      service = TestBed.inject(SnackbarService);
      matSnackBar = TestBed.inject(MatSnackBar);
    });
    

    你的测试看起来像这样,监视open 方法并验证它被调用的参数:

    it('calls the MatSnackBar open method with status, "x", and a config object', () => {
      const matSnackBarSpy = spyOn(matSnackBar, 'open').and.stub();
    
      service.openSnackBar('arg');
    
      expect(matSnackBarSpy.calls.count()).toBe(1);
    
      const args = matSnackBarSpy.calls.argsFor(0);
      expect(args.length).toBe(3);
      expect(args[0]).toBe('another status');
      expect(args[1]).toBe('x');
      expect(args[2]).toEqual({
        prop2: 6000,
        horizontalPosition: 'center',
        verticalPosition: 'top',
        prop3: 'success'
      });
    });
    

    Here's a StackBlitz 展示了这种方法。

    【讨论】:

    • 感谢您的意见。我已经写了一些基本的测试并发布了一个答案,但实际上很高兴看到你更富有我认为接近并与我的比较。你能看看并评论它吗?我会重复使用您的 StackBliz 并粘贴链接。
    • @mpro,根据您的要求,我在您发布的答案中添加了评论。 :) 我希望它会有所帮助。
    【解决方案2】:

    您可以验证它是否应该打开。

    it('should open snack bar', () => {
      // use this for fake call or return value or use next line
      spyOn(service, 'openSnackBar').and.callThrough(); 
      // it will call the service 
      service.openSnackBar(); 
      expect(service.openSnackBar('open this')).toHaveBeenCalled();
    });
    

    【讨论】:

    • 不幸的是Error: &lt;toHaveBeenCalled&gt; : Expected a spy, but got undefined.
    • 已更新答案,请立即查看。
    【解决方案3】:

    根据昨天的一些提示(有些已经消失),Jasmine docsthis post 我能够编写一些基本测试(在 cmets 和 working StackBlitz 中的描述)

    describe("SnackbarService", () => {
      let service: SnackbarService;
      // mocking service
      const mockSnackbarService = jasmine.createSpyObj("SnackbarService", [
        "openSnackBar"
      ]);
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          // providing the service and mock into the Testbed
          providers: [{ provide: SnackbarService, useValue: mockSnackbarService }]
        });
        // injecting the service
        service = TestBed.inject(SnackbarService);
      });
    
      it("should be created", () => {
        expect(service).toBeTruthy();
      });
    
      describe("openSnackBar", () => {
        it("should open the snack bar", () => {
          const arg = "test";
          // calling mock
          mockSnackbarService.openSnackBar.and.callThrough();
          // calling service method with argument
          service.openSnackBar(arg);
          // checking if method has been called
          expect(service.openSnackBar).toHaveBeenCalled();
        });
      });
    });
    

    【讨论】:

    • 你不应该嘲笑 SnackbarService 如果那是你正在测试的代码,你应该嘲笑它的依赖,否则你没有执行(和测试)应用程序代码。这个测试不是很有用,因为你在你的测试中调用了一个方法,然后expecting 这个方法已经被调用了,但是你知道它被调用了,因为你刚刚调用了它。一个有用的测试是在你的测试中调用一个服务方法,expect 应用程序代码应该做的事情实际上已经完成了,比如调用一些依赖项的方法。但你甚至没有在这里练习应用代码。
    猜你喜欢
    • 1970-01-01
    • 2019-04-12
    • 1970-01-01
    • 2017-11-30
    • 2013-06-11
    • 2017-11-19
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多