Categories
Angular

Simpler unit tests in Angular

The Angular command line interface (ng-cli) generates the code for a component for you, including html, stylesheet, component code and a unit test. It will generate a unit test, I added the necessary import and mocked service:

describe('PortfolioComponent', () => {
  let component: PortfolioComponent;
  let fixture: ComponentFixture<PortfolioComponent>;
  let stockService = mock(StockService);

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [BrowserModule, BrowserAnimationsModule, FormsModule,
        ButtonModule, CalendarModule, DialogModule, DropdownModule, PanelModule, TableModule],
      declarations: [ PortfolioComponent ],
      providers: [
        {provide: StockService, useValue: instance(stockService)}
      ],
    })
    .compileComponents();
  }));

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

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

The unit test above will setup a TestBed with imports and providers to make it possible to render the component. This will make sure that the HTML template will work with your component code. This is all great, but it comes with a price. When I do this, it takes more time to figure out which modules and services it needs. It also takes a bit longer to setup the TestBed and execute the unit test. On my machine this simple test took 550 milliseconds. I used ts-mockito to mock the service.

describe('PortfolioComponent (Unit test)', () => {
  let component: PortfolioComponent;
  let stockService = mock(StockService);

  beforeEach(async(() => {
    component = new PortfolioComponent(instance(stockService));
  }));

  it('ngOnInit', () => {
    component.ngOnInit();

    expect(component.portfolioRows.length).toBe(2);
  });

  it('refreshPortfolioPrices', () => {
    let response0 = new StockLatestPriceResponse();
    response0.latestPrice = 1.23;
    when(stockService.getStockLatestPrice("AAPL")).thenReturn(of(response0));
    let response1 = new StockLatestPriceResponse();
    response1.latestPrice = 2.34;
    when(stockService.getStockLatestPrice("GOOG")).thenReturn(of(response1));

    component.ngOnInit();
    component.refreshPortfolioPrices();

    expect(component.portfolioRows.length).toBe(2);
    expect(component.portfolioRows[0].latestPrice).toBe(1.23);
    expect(component.portfolioRows[1].latestPrice).toBe(2.34);
  });
});

Instead of setting up the TestBed, you just instantiate the component with its constructor and mocked service. You can run the same tests and this time it takes only 11 milliseconds. Compared with the TestBed, this takes only 1/50th of the time. If you have a substantial amount of tests, this save quite a lot of time.

The drawback is of course that you don’t test the component template. You can separate tests that include these templates with the TestBed; I have created portfolio.component.spec.ts and portfolio.component.unit.spec.ts to separate component and unit test.

See my Github repository: https://github.com/koert/stock-portfolio/tree/master/spring-boot/src/main/angular/src/app/portfolio

Conclusion:
A pure unit test without TestBed:
– doesn’t test component template
– not dependent on module import, less brittle
– is 50 times faster

Categories
Angular

Unit testing Angular components

When you create an Angular application and componentes with ths NG command line interface, it creates unit tests for you. The support for unit testing is great, you just run ng test and you will see the results.

angular-mock-test> ng test
 11% building 9/9 modules 0 active24 01 2020 10:52:16.444:WARN [karma]: No captured browser, open http://localhost:9876/
24 01 2020 10:52:16.448:INFO [karma-server]: Karma v3.1.4 server started at http://0.0.0.0:9876/
24 01 2020 10:52:16.448:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
24 01 2020 10:52:16.453:INFO [launcher]: Starting browser Chrome
24 01 2020 10:52:20.714:WARN [karma]: No captured browser, open http://localhost:9876/
24 01 2020 10:52:20.794:INFO [Chromium 79.0.3945 (Linux 0.0.0)]: Connected on socket PQFYob1QwIhZxRoMAAAA with id 41864170
Chromium 79.0.3945 (Linux 0.0.0): Executed 9 of 9 SUCCESS (0.253 secs / 0.243 secs)
TOTAL: 9 SUCCESS
TOTAL: 9 SUCCESS

Mocking services

In many components, you would use a service to retrieve data. In the unit test it is useful to isolate the component from its dependencies like a service with mocks. The ts-mockito library makes it easy to create mocks, control their behavior, and check it they are called correctly.

You first create a mock object:

let mockMyTestService = mock(MyTestService);

With this object you can control the simulated responses of the mock. To make the mocked service available to the component, you create an instance.

providers: [
  {provide: MyTestService, useValue: instance(mockMyTestService)},
]

This way, the component uses an instance of the mock. Then you can program a response with when and thenReturn.

beforeEach(() => {
  fixture = TestBed.createComponent(Test1Component);
  component = fixture.componentInstance;
  when(mockMyTestService.getHello()).thenReturn(of("hello from test"));

  fixture.detectChanges();
});

You can check if the mock was accessed with verify.

verify(mockMyTestService.getHello()).times(1);

Mock child component

Suppose that you use a child component. Test1Component uses Test2Component as a child:

<test2 #testChild2></test2>
@ViewChild("testChild2") private test2Component: Test2Component;

It would be nice if we can isolate Test2Component with a mock.

TestBed.configureTestingModule({
  declarations: [ Test1Component, instance(mockTest2Component) ],
  providers: [
    {provide: MyTestService, useValue: instance(mockMyTestService)},
  ]
})

You will get this error message: Error: Unexpected value '[object Object]' declared by the module 'DynamicTestModule'.

I have solved this by using the ng-mocks library. Use MockComponent() to create a mock.

TestBed.configureTestingModule({
  declarations: [ Test1Component, MockComponent(Test2Component) ],
  providers: [
    {provide: MyTestService, useValue: instance(mockMyTestService)},
  ]
})

With both of the mock libraries you can properly isolate your component and make unit testing much easier.

You can take a look at the code in my repository at Github.

Categories
Angular

Electron with Angular 2

Electron logo

Since Angular 2 is finally released, we can now use it to create a production-ready web application. I recently noticed the Electron framework (http://electron.atom.io/) – it allows you to develop desktop applications using NodeJS and the JavaScript framework of your choice.

A project that I am working on needs start helper (Windows) applications and access to local files, so I decided to start developing an Electron application with Angular 2.

It seems a bit hard to find a starting point for an Electron/Angular project. I found a boilerplate project on GitHub, and unfortunately the Angular dependencies are out of date. I forked that boilerplate and fixed the dependencies. This is my repository:

https://github.com/koert/angular2-electron-boilerplate