Mocking Ionic Native Support in Browser

2019-07-23 05:05发布

问题:

Trying to mock Ionic Native Network in browser. To do so, I added the class in my app.module.ts as follows:

    ...
    import { Network } from '@ionic-native/network';
    ...
    ...    
    export class NetworkMock extends Network{      
      type = 'none';
    }
@NgModule({ ....


providers: [ 
...
...

{ provide: ErrorHandler, useClass: IonicErrorHandler },
{provide: Network, useClass: NetworkMock}

With the above, when I call the following function in a component:

check_network() {    
    console.log(this.net.type);    
}

I get null in the browser console

How can I get it working correctly ? Am i not supposed to get 'none' ?

The following is the output from ionic-info :

global packages:

    @ionic/cli-utils : 1.4.0
    Cordova CLI      : 6.5.0
    Ionic CLI        : 3.4.0

local packages:

    @ionic/app-scripts              : 1.3.0
    @ionic/cli-plugin-cordova       : 1.4.0
    @ionic/cli-plugin-ionic-angular : 1.3.1
    Cordova Platforms               : none
    Ionic Framework                 : ionic-angular 3.0.1

System:

    Node       : v6.9.1
    OS         : Windows 7
    Xcode      : not installed
    ios-deploy : not installed
    ios-sim    : not installed
    npm        : 3.10.8

What is going wrong ? Please help.

回答1:

I prefer to do things a little bit different when it comes to using a cordova plugin in the browser. Even though your approach works great, you still need to manually tell Angular which class should be used when it sees a Network parameter in some component's constructor. You do that in this line:

{ provide: Network, useClass: NetworkMock } 

So if you want to run the app on a mobile device, you'd need to manually change that line (and all the lines related to some other cordova plugins as well).

That's why I like to use a factory provider instead. This is how I work with the Network cordova plugin in the browser:

// ------------------------------------------------------------
// File: /providers/cordova-plugins/network/network.provider.ts
// ------------------------------------------------------------

// Ionic
import { Platform } from 'ionic-angular';

// Ionic native
// http://ionicframework.com/docs/v2/native/network/
import { Network } from '@ionic-native/network';

// Browser implementation
export class BrowserNetworkProvider extends Network {

    public get type(): string {
        return this.isOnline() ? 'wify' : 'none';
    }

    private isOnline(): boolean {
        return navigator.onLine;
    }
}

// Mobile implementation
// Is empty in this case since I don't to override anything, but in 
// some other plugins, I like to add some console.log() before calling
// the methods in the plugin (by using super.nameOfTheMethod();)
export class MobileNetworkProvider extends Network {}

// ------------------------------------------------------------
// Network factory
//    parameters: dependencies of the target service
//    returns: instance of the service (for real devices or the browser)
// ------------------------------------------------------------
export function networkFactory(platform: Platform) {
    return platform.is('cordova') ? new MobileNetworkProvider() : new BrowserNetworkProvider();
}

// networkProvider: used to import the service in the NgModule declaration
export let networkProvider =
    {
        provide: Network,
        useFactory: networkFactory,
        deps: [Platform]
    };

And then in the app.module.ts file:

import { networkProvider } from '../providers/plugins/network/network.provider';

@NgModule({
    declarations: [
        MyApp,
        //...
    ],
    imports: [
        // ...
    ],
    bootstrap: [IonicApp],
    entryComponents: [
        // ...
    ],
    providers: [
        // Cordova plugins
        networkProvider, // <- I'm using our custom provider here! :)

        // ...
    ]
})
export class AppModule { }

As you can see, Angular will use the Platform information to know which extension of the Network class should be used in our app.


As we talked about in the comments:

1) Regarding the getter to override the type property, I don't know that much of Typescript, but for some reason I was not able to set the value of a property when extending a class (that's why I suggested using the getter instead). Since a getter is called just like if it were a property, I though about doing it like that and seems to be working!

2) Why extending the Network class for the browser, when we could also use a class that has nothing to do with the Network class (like when you omit the extend keyword in your example? Well, you could do that, since after all this is just Javascript (and you can fix any Typescript complain using the (<any>propertyName) cast. But since you're telling Angular which implementation of the Network class should be used, why not extending that class, overriding what we want to change, but keeping everything consistent? If later the plugin add some methods that works on the browser, you won't need to change anything, since you're already using the real implementation of the plugin for all the methods that you don't override.



回答2:

I just tried the following alternative and it worked.

...
import { Network } from '@ionic-native/network';
...
...    
export class NetworkMock{   
 // I omitted the Extend keyword and avoided overriding the actual class   
 type = 'none';
}
@NgModule({ ....


providers: [ 
...
...

{ provide: ErrorHandler, useClass: IonicErrorHandler },
{provide: Network, useClass: NetworkMock}

With the code above, now the browser console correctly prints 'none' or whatever value I put in the type property of the NetworkMock class.

To my understanding, the Inheritance is Javascript is tricky in nature and comes up with undesirable circumstances. So I avoided it completely. However, in the providers array I referenced my NetworkMock in useClass property.

Any further insight will be highly appreciated.