Unit testing where(this).use(that)


#1

Hi all,
TL;DR; What is best practice do something like where(this).use(that) in Ionic 2/Angular 2?

For example, I used:
test.ts

    import {BarcodeScanner} from 'ionic-native';

    extend class Test {
      public openScan(_BarcodeScanner?): Promise<any> {
        if (!_BarcodeScanner) {
          _BarcodeScanner = BarcodeScanner;
        }

        return _BarcodeScanner.scan().then((barcodeData) => {});
      }
    }

test.spec.ts

    class MockBarcodeScanner {
      public static scan(): Promise<any> {
        return new Promise((resolve, reject) => resolve({
          'cancelled': false,
          'format': 'QR_CODE',
          'text': 'any text'
        }));
      }
    }

    export function main(): void {
      'use strict';
      
      beforeAll(() => {
        test = new Test();
      });

      describe('Test', () => {
       it('Get something text', () => {
          test.openScan(MockBarcodeScanner).then((response) => {
            expect(response.text).toBe('something');
          });
        });
      })
    }

It works but I changed my original code for the test here

openScan(_BarcodeScanner?)

and pass the mock like argument from the test

BUT Maybe I can explain to the test something like: if it call BarcodeScanner, then it use MockBarcodeScanner? And I no need send the mock like argument?

Thanks you.

PS Sorry for my bad English.


#2

If I understand correctly, you are trying to use a mocked BarcodeScanner or similar when testing. In order to test it in this example you changed your code to support passing in the BarcodeScanner and you are trying to find a way to do it where you do not have to pass the BarcodeScanner.

If this is correct, you should use Angular2’s Dependency Injection framework which is build in to Ionic 2. You would want to do something like this:

import {BarcodeScanner} from 'ionic-native';
import {Injectable} from 'angular2/core';

@Injectable()
extend class Test {

    private barcodeScanner: BarcodeScanner;

    constructor(barcodeScanner: BarcodeScanner) {
        this.barcodeScanner = barcodeScanner;
    }

    public openScan(): Promise<any> {
        return this.barcodeScanner.scan().then((barcodeData) => {});
    }
}

test.spec.ts

class MockBarcodeScanner {
    public static scan(): Promise<any> {
        return new Promise((resolve, reject) => resolve({
            'cancelled': false,
            'format': 'QR_CODE',
            'text': 'any text'
        }));
    }
}

export function main(): void {
          
    beforeEachProviders(() => [
        provide((BarcodeScanner), {useClass: MockBarcodeScanner}),
        Test
    ]);

    describe('Test', () => {
        it('Get something text', inject([Test], (test: Test)=> {
            test.openScan(MockBarcodeScanner).then((response) => {
                expect(response.text).toBe('something');
            });
        });
  })
}

The DI framework will create an instance of BarcodeScanner to be used by the Test class when the Test class is injected. The constructor of Test does not need to be called by you as it is handled by the DI framework. In the spec, adding the beforeEachProviders block will provide the two specified providers (BarcodeScanner and Test in this example), however the BarcodeScanner reference now will refer to MockBarcodeScanner. When Test is injected in this reference it will be given the MockBarcodeScanner rather than the standard.

See Pascal Precht’s blog for better understanding of DI in Angular2 (and by extension Ionic 2)


#3

You understand correctly. Thank you.

But if the Test class is @Page and not service?


#4

I’m not really sure, as the @Page elements don’t appear to be intended for injection. I would probably still do the beforeEachProviders call providing the barcode scanner mock, but not Test, and inside of the it block I would inject a barcode scanner and create a new test using the constructor, passing it the barcode scanner.

Something like:

export function main(): void {
      
    beforeEachProviders(() => [
        provide((BarcodeScanner), {useClass: MockBarcodeScanner})
    ]);

    describe('Test', () => {
          // This will inject BarcodeScannerMock as we are providing that in place of
          // BarcodeScanner
        it('Get something text', inject([Test], (barcodeScanner: BarcodeScanner)=> {
            let test = new Test(barcodeScanner);
            test.openScan(MockBarcodeScanner).then((response) => {
                expect(response.text).toBe('something');
            });
        });
    })