【问题标题】:Include component template in unit test coverage with Angular使用 Angular 在单元测试覆盖率中包含组件模板
【发布时间】:2018-02-13 12:39:59
【问题描述】:

使用 Angular 4 可以测试组件的模板,例如检查单击按钮是否触发了预期的方法等。

但是如何将模板包含在测试覆盖中呢?默认情况下它们不是(使用带有 Karma+Jasmine+Istanbul 的 Angular CLI 测试)。

【问题讨论】:

  • 如果您通过角度测试使用 TestBed,那么组件也包含在测试中,您可以单独测试它们...它更多的是集成测试,而不是使用 new 关键字创建组件。跨度>
  • 它不能衡量我的ngIfs 和ngFors 的测试,是吗?

标签: angular jasmine karma-runner code-coverage istanbul


【解决方案1】:

在我看来,你只能测试你点击时调用的函数,因为你不需要验证如果你点击了angular会调用这个函数。

但如果你想以任何方式测试它,你可以这样做

import { TestBed, async, ComponentFixture } from '@angular/core/testing';

describe('', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ ],
      declarations: [ TestComponent ],
      providers: [  ]
    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(TestComponent);
      component = fixture.componentInstance;
    });
  }));
});

it('should click on button', async(() => {
  spyOn(component, 'onEditButtonClick');

  let button = fixture.debugElement.nativeElement.querySelector('button');
  button.click(); 
// here you can test you code or just check that your function have been called like in the example bellow 

  fixture.whenStable().then(() => {
    expect(component.onEditButtonClick).toHaveBeenCalled();
  });
}));

【讨论】:

    【解决方案2】:

    您是否有意测试模板的实际表示?然后你必须完全切换到玩笑而不是业力/茉莉花组合。使用 jest,您可以在测试中生成模板快照,这些快照与组件一起推送并在 CI 测试中进行检查。

    例如,假设您有一个 main.component,它在 SessionService 未准备好时显示加载器,然后在完成后显示路由内容的路由器出口。一个开玩笑的快照看起来有点像这样:

    exports[`MainComponent renders: default state 1`] = `
    <main
      isReady$={[Function BehaviorSubject]}
      sessionService={[Function Object]}
    >
      <loader-line
        ng-reflect-active="true"
      />
    </main>
    `;
    
    exports[`MainComponent session storage is ready renders 1`] = `
    <main
      isReady$={[Function BehaviorSubject]}
      sessionService={[Function Object]}
    >
      <main>
        <router-outlet />
      </main>
    </main>
    `;
    

    而测试代码是这样的:

    describe('MainComponent', () => {
      let fixture: ComponentFixture<MainComponent>;
    
      const isReady$ = new BehaviorSubject(false);
      const mockSessionStorage = Mock.all<SessionService>();
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [MainComponent, MockComponents(LoaderLineComponent)],
          imports: [RouterTestingModule],
          providers: [mockWith(SessionService, mockSessionStorage)],
        })
          .compileComponents();
      }));
    
      afterAll(() => {
        isReady$.complete();
      });
    
      beforeEach(() => {
        Mock.extend(mockSessionStorage).with({isReady$});
        fixture = TestBed.createComponent(MainComponent);
        detectChanges(fixture);
      });
    
      it(`creates instance`, () => expect(fixture.componentInstance).toBeTruthy());
    
      it(`renders`, () => expect(fixture).toMatchSnapshot(`default state`));
    
      describe(`session storage is ready`, () => {
        beforeEach(() => {
          isReady$.next(true);
          detectChanges(fixture);
        });
    
        it(`renders`, () => expect(fixture).toMatchSnapshot());
      });
    });
    

    就是这样,不再在spec文件代码中查询&lt;loader-line&gt;&lt;router-outlet&gt;,只需查看快照即可。

    注意:我也在使用 ng-mocks、ts-mockery 和一些自己的 util 函数,但您要寻找的主要是 expect(fixture).toMatchSnapshot() 行,它们是 jest native。

    【讨论】:

      【解决方案3】:

      在我看来,您应该重新考虑要测试什么以及如何测试。您可以测试是否从组件内部触发了某些内容(单元测试),例如如果我调用此函数,则此属性会更改值。

      export class Page {
        listOfSomething: string[] = [];
      
        addStuff: (item: string) => this.listOfSomething.push(item);
      }
      

      在这里您可以测试listOfSomething 是否随时间变化。

      要知道一个按钮是否从模板中的button 执行此操作,那么您可能会遇到这种情况

      <p *ngFor="let item of listOfSomething">{{ item }}<p>
      
      <button (click)="addStuff('stuff')">add stuff</button>
      

      在这种情况下,您希望看到单击按钮后屏幕上的元素数量会发生变化。基本上你是在间接检查addStufflistOfSomething,但仍在检查它们。

      --

      总的来说,您需要将测试拆分为单元测试和 e2e 测试。 Jasmine 更适合单元测试。你可能会找到办法,但不值得花时间。

      您可以在下面看到登录页面 (e2e) 所需的不同方法。

      import { browser, by, element } from 'protractor';
      
      export const testData = {
        userName: 1231232,
        password: 'pass1234'
      };
      
      export class LoginPage {
        //////////////////////////////////////////////////////////////////////////////////////////
        // navigate //////
      
        navigateTo() {
          return browser.get('/login');
        }
      
        quickLogin() {
          this.navigateTo();
          this.setMobileNumber(testData.userName);
          this.setPassword(testData.password);
          this.doLogin();
        }
      
        //////////////////////////////////////////////////////////////////////////////////////////
        // selectors /////
      
        getLoginButton() {
          return element(by.buttonText('Log in'));
        }
      
        //////////////////////////////////////////////////////////////////////////////////////////
        // getText ///////
      
        getSelectedMobileNumberPrefix() {
          return element(by.deepCss('section form .mat-select .value-output')).getText();
        }
      
        getErrorMessage() {
          return element(by.deepCss('snack-bar-container simple-snack-bar span')).getText();
        }
      
        //////////////////////////////////////////////////////////////////////////////////////////
        // sendKeys //////
      
        setMobileNumber(phoneNumber: number) {
          return element(by.css('[formControlName=phoneNumber]')).sendKeys(phoneNumber);
        }
      
        setPassword(password: string) {
          return element(by.css('[formControlName=password]')).sendKeys(password);
        }
      
        //////////////////////////////////////////////////////////////////////////////////////////
        // click /////////
      
        doLogin() {
          return this.getLoginButton().click();
        }
      }
      
      import { browser, protractor } from 'protractor';
      
      import { DashboardPage } from '../dashboard/dashboard.po';
      import { LoginPage } from './login.po';
      
      const password = {
        correct: 'pass1234',
        wrong: 'xxx',
      };
      
      const testData = {
        maleUser: 1231232,
        femaleUser: 1231231,
      };
      
      describe('Login Journey', () => {
        const EC = protractor.ExpectedConditions;
        let dashboardPage: DashboardPage;
        let loginPage: LoginPage;
      
        beforeEach(() => {
          dashboardPage = new DashboardPage();
          loginPage = new LoginPage();
        });
      
        afterEach(() => {
          browser.executeScript('window.localStorage.clear();');
        });
      
        it('Checking init page', () => {
          loginPage.navigateTo();
      
          expect(loginPage.getSelectedMobileNumberPrefix()).toContain('+60');
          expect(loginPage.getLoginButton().isEnabled()).toBeFalsy();
      
          loginPage.setMobileNumber(testData.maleUser);
          loginPage.setPassword(password.correct);
      
          expect(loginPage.getLoginButton().isEnabled()).toBeTruthy();
          expect(loginPage.getSelectedMobileNumberPrefix()).toContain('+60');
        });
      
        it('I should be able to login', () => {
          loginPage.navigateTo();
      
          loginPage.setMobileNumber(testData.maleUser);
          loginPage.setPassword(password.correct);
          loginPage.getLoginButton().click();
      
          browser.waitForAngular();
      
          expect(dashboardPage.getTitle()).toContain('Dashboard');
        });
      
        it('I should NOT be able to login with incorrect credentials', () => {
          loginPage.navigateTo();
      
          loginPage.setMobileNumber(testData.maleUser);
          loginPage.setPassword(password.wrong);
          loginPage.getLoginButton().click();
      
          browser.waitForAngular();
      
          expect(loginPage.getErrorMessage()).toContain('The email address or the password you inputted is incorrect.');
        });
      });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-03
        • 2018-10-18
        相关资源
        最近更新 更多