Accessing angular service constructor in Jest testing framework - unit-testing

I'm struggling to find an answer to the following (and I'll bet it's more than likely that the answer is relatively straightforward)...
I'm unit testing an Ionic 3.4 application using the Jest testing framework and can't figure out how to call an angular service for testing where the constructor initialises the Http object and an InAppBrowser Ionic Native plugin object.
My package.json file contains the following configuration:
...
"jest": {
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
]
},
"devDependencies": {
"#types/jest": "^20.0.2",
"#types/node": "^7.0.31",
"jest": "^20.0.4",
"ts-jest": "^20.0.6",
"ts-node": "^3.0.6",
"tslint": "^5.4.3",
"tslint-eslint-rules": "^4.1.1",
"typescript": "2.3.3"
},
...
My tsconfig.json file, located in the src/unit-tests directory (so as to only target that directory and its contents and not interfere with the root tsconfig.json file for the Ionic project), is as follows:
{
"compilerOptions" : {
"module" : "commonjs",
"allowSyntheticDefaultImports" : true,
"experimentalDecorators" : true,
"noImplicitAny" : false,
"removeComments" : false,
"locale" : "en-GB",
"alwaysStrict" : true,
"skipLibCheck" : true,
"pretty" : true
}
}
The requested service - providers/settings/settings.ts - is as follows:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { InAppBrowser } from '#ionic-native/in-app-browser';
import 'rxjs/add/operator/map';
#Injectable()
export class SettingsProvider {
constructor(public http : Http,
private _INAPP : InAppBrowser)
{ }
// Methods follow here
}
This is then used within the settings.test.ts file like so:
import 'reflect-metadata';
import { SettingsProvider } from '../providers/settings/settings';
let settings = null;
beforeEach(() => {
// The following works IF the SettingsProvider class
// contains NO parameters in the constructor
settings = new SettingsProvider();
});
describe('Settings service', () =>
{
// Tests are defined within here
// using methods supplied by the
// SettingsProvider class
});
IF I take the parameters out of the SettingsProvider class constructor Jest can run the tests exactly as intended (pass or fail).
With the parameters in place inside the SettingsProvider class constructor Jest chokes when trying to run the test script with the following error:
Test suite failed to run
/ionic-unit/node_modules/#ionic-native/in-app-browser/index.js:20
import { Injectable } from '#angular/core';
^^^^^^
SyntaxError: Unexpected token import
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/ScriptTransformer.js:289:17)
at Object.<anonymous> (src/providers/settings/settings:12:21)
at Object.<anonymous> (src/unit-tests/settings.test.ts:2:18)
How do I configure my test script so that Jest recognises the modules that are initialised in the SettingsProvider constructor?
I'm relatively new to unit testing and have been trying to figure out how to accomplish this (but, as you can tell, without much joy so far!)
I appreciate any guidance/advice any developers out there might have on the matter.
Thanks!

I haven't used Jest yet - I usually work with jasmine/karma, the default from the #angular team - but can't you use the Testbed to configure it?
With karma/jasmine, you can do something like this:
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
Http,
InAppBrowser
],
})
}));
beforeEach(async(() => TestUtils.beforeEachCompiler([]).then(compiled => {
fixture = compiled.fixture;
instance = compiled.instance;
fixture.autoDetectChanges(true);
})));

Related

How to write unit test in nestjs and typeorm without connecting to db

I am not sure how to implement unit test in nestjs and typeorm without connecting to db. I have tried a number of technic but non seem to work.
My module looks something like this.
import { HttpModule, Module } from '#nestjs/common'
import moment from 'moment';
import config from '#app/config'
import { OrdersService } from './services/order.service'
import { FraudOrderChecksService } from './services/fraud-order-checks.service'
import { FraudOrderChecksController } from './controllers/fraud-order-checks.controller'
import { HealthcheckController } from './controllers/healthcheck.controller';
import { TypeOrmModule } from '#nestjs/typeorm'
import { ormconfig } from './entities/ormconfig'
#Module({
imports: [
SharedModule,
HttpModule,
LoggerModule,
ConfigModule.forRoot(config),
TypeOrmModule.forRoot(ormconfig.luminskin as any),
TypeOrmModule.forRoot(ormconfig.meridian as any),
TypeOrmModule.forFeature([...ormconfig.luminskin.entities], 'luminskin'),
TypeOrmModule.forFeature([...ormconfig.meridian.entities], 'meridian'),
...
],
controllers: [
MyController,
...
],
providers: [
...
],
})
export class AppModule { }
I import the root module in my test
beforeEach(async () => {
jest.clearAllMocks();
const module: TestingModule = await Test.createTestingModule({
imports: [InternalModule]
}).compile();
...
});
When I try to run my unit test I get
[Nest] 93196 - 06/04/2021, 17:43:50 [ExceptionHandler] Unable to connect to the database (mydb). Retrying (1)...
AlreadyHasActiveConnectionError: Cannot create a new connection named "connectionname", because connection with such name already exist and it now has an active connection session.
How do I decouple the connection from the root module, so it is only ran when needed. Actually cleaner technic will also be accepted.
You don't want to unit test a module, you want to unit test a modules individual components in isolation.
Although you can create a TestModule and simply import your module as you have done above, I would only consider doing that when the module contained a single component (even then I wouldn't as I don't think its very good practice).
The more components you bring into your test:
The more moving parts you need to manage
The more you have to mock
The less portability you have with the unit and its test
The more aspirin you ingest trying resolve self induced headaches that occur every time you modify its parent module
Nests TestingModule enables you to "rig" up an independent module with the bare minimum needed to test your component in isolation. It simplifies your test setups and mock creation/management.
Always try to look at unit testing as a stand alone, independent processes. Limit the scope and dependencies wherever possible to make testing as effective and easy as possible.
Here is an example of the approach I take for unit testing a service where I mock out its dependencies:
// app.service.spec.ts
describe('Testing app.service', () => {
let module: TestingModule;
let service: AppService;
// mock out providers the service depends on
const mockProviders = [
{
provide: ConfigService,
useValue: {
get: jest.fn().mockReturnValue('Mock!'),
},
},
];
beforeAll(async () => {
// build up testing module
module = await Test.createTestingModule({
imports: [],
providers: [...mockProviders, AppService],
})
.compile()
.catch((err) => {
// Helps catch ninja like errors from compilation
console.error(err);
throw err;
});
service = module.get<AppService>(AppService);
});
it('Should return: Hello Mock!', async () => {
const response = service.getHello();
expect(response).toEqual('Hello Mock');
});
});
I try to keep all business logic (wherever possible) in services, leavingcontrollers light and generally reserved for e2e and/or integration testing.
This isn't the only (and maybe not even "the best") approach, but it helps me to keep my tests and services more focused.

How to access Vuejs methods to test with Jest?

I have the following file: deposit-form.js.
With the following code:
new Vue({
el: '#app',
data: {
title: 'title',
depositForm: {
chosenMethod: 'online',
payMethods: [
{ text: 'Already paid via Venmo', value: 'venmo' },
{ text: 'Pay online', value: 'online' },
{ text: 'In-person payment', value: 'person' }
],
},
},
methods: {
submitDeposit: function() {
$.ajax({
url: 'http://localhost:8000/api/v1/deposit/',
type:'post',
data: $('#deposit-form').serialize(),
success: function() {
$('#content').fadeOut('slow', function() {
// Animation complete.
$('#msg-success').addClass('d-block');
});
},
error: function(e) {
console.log(e.responseText);
},
});
},
showFileName: function(event) {
var fileData = event.target.files[0];
var fileName = fileData.name;
$('#file-name').text('selected file: ' + fileName);
},
},
});
I'm having problems on how to setup Jest, how to import the VueJs functions inside 'methods' to make the tests with Jest.
How should be my code on the deposit-form.test.js ?
The first thing you need to do is export Vue app instance.
// deposit-form.js
import Vue from 'vue/dist/vue.common';
export default new Vue({
el: '#app',
data: {...},
...
});
Now you can use this code in your spec file. But now you need to have #app element before running tests. This can be done using the jest setup file. I will explain why it's needed. When you import your main file (deposit-form.js) into a test, an instance of Vue is created in your main file with new. Vue is trying to mount the application into #app element. But this element is not in your DOM. That is why you need to add this element just before running the tests.
In this file you also can import jQuery globally to use it in your tests without import separately.
// jest-env.js
import $ from 'jquery';
global.$ = $;
global.jQuery = $;
const mainAppElement = document.createElement('div');
mainAppElement.id = 'app';
document.body.appendChild(mainAppElement);
Jest setup file must be specified in the jest configuration section in package.json.
// package.json
{
...,
"dependencies": {
"jquery": "^3.3.1",
"vue": "^2.6.7"
},
"devDependencies": {
"#babel/core": "^7.0.0",
"#babel/plugin-transform-modules-commonjs": "^7.2.0",
"#babel/preset-env": "^7.3.4",
"#vue/test-utils": "^1.0.0-beta.29",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.1.0",
"babel-loader": "^8.0.5",
"babel-preset-vue": "^2.0.2",
"jest": "^24.1.0",
"vue-jest": "^3.0.3",
"vue-template-compiler": "^2.6.7",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3"
},
"scripts": {
"test": "./node_modules/.bin/jest --passWithNoTests",
"dev": "webpack --mode development --module-bind js=babel-loader",
"build": "webpack --mode production --module-bind js=babel-loader"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
},
"setupFiles": [
"<rootDir>/jest-env.js"
]
}
}
Also, you probably need to configure Babel to use the features of ES6 in your projects and tests. This is not necessary if you follow the commonjs-style in your code. Basic .babelrc file contains next code:
// .babelrc
{
"presets": [
[
"#babel/preset-env",
{
"useBuiltIns": "entry",
"targets": {
"browsers": [
"last 2 versions"
]
}
}
],
"vue",
],
"plugins": [
"#babel/plugin-transform-modules-commonjs",
]
}
Now you can write your tests.
// deposit-form.test.js
import App from './deposit-form';
describe('Vue test sample.', () => {
afterEach(() => {
const mainElement = document.getElementById('app');
if (mainElement) {
mainElement.innerHTML = '';
}
});
it('Should mount to DOM.', () => {
// Next line is bad practice =)
expect(App._isMounted).toBeTruthy();
// You have access to your methods
App.submitDeposit();
});
});
My recommendation is to learn Vue Test Utils Guides and start to divide your code into components. With the current approach, you lose all the power of components and the ability to test vue-applications.
I updated my answer a bit. As I understood from the comment to the answer, you connect the libraries on the page as separate files. Here is my mistake. I didn't ask if the build system is being used. Code in my examples is written in the ECMA-2015 standard. But, unfortunately, browsers do not fully support it. You need an transpiler that converts our files into a format that is understandable for browsers. It sounds hard. But it's not a problem. I updated the contents of the file package.json in response. Now it only remains to create an input file for the assembly and run the assembly itself.
The input file is simple.
// index.js
import './deposit-form';
The build is started with the following commands from terminal.
# for development mode
$ yarn run dev
# or
$ npm run dev
# for production mode
$ yarn run build
# or
$ npm run build
The output file will be placed in the directory ./dist/. Now instead of separate files you need to connect only one. It contains all the necessary for the library and your code.
I used webpack to build. More information about it can be found in the documentation. Good example you can find in this article.

How to add service to Angular2 test (CLI config, not as in docs)

I'm just now learning unit testing in general, but I'm simultaneously learning angular2. Angular CLI created my test scaffolds and I've configured the testing module with the neccessary component and directive. I'm trying to provide a stub service similar to the one decribed in the DOCS, but I'm getting the following error:
Error: Cannot create the component FoodListComponent as it was not imported
into the testing module
This is odd because the should create test passed when with the FoodListComponent before I added the service into the providers: []. My service works fine in the application, but I can provide it too if you need to see it.
Basically I'd like to know where in this testing set up I need to put the foodDataServiceStub, whether I'm adding the stub service to the TestBed object correctly, and where I need to inject the service via foodDataService = TestBed.get(FoodDataService);?
I'd be thankful for an explanation as well.
food-list.component.spec.ts
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement, NO_ERRORS_SCHEMA } from '#angular/core';
import { TickerDirective } from '../../directives/ticker.directive';
import { FoodListComponent } from './food-list.component';
describe('FoodListComponent', () => {
let component: FoodListComponent;
let fixture: ComponentFixture<FoodListComponent>;
let foodDataServiceStub = {
name: 'test food name',
img: '',
description: 'test food description'
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FoodListComponent, TickerDirective ],
providers: [ { provide: FoodDataService, useValue: foodDataServiceStub } ], /* This line gives me the error */
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FoodListComponent);
component = fixture.componentInstance;
// foodDataService = TestBed.get(FoodDataService);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Ionic 2 rc0 whitescreen (No provider for ConnectionBackend!)

I started a new ionic 2 rc0 app by copying the files of my old beta11 project. I did the necessary steps as described under
Copying your Project to a New Project : ionic 2 rc0 changelog
Now finally after getting no more compiler errors when I run
ionic run android -c
I'm just getting a whitescreen on my android phone.
Chrome debug is logging me
Uncaught Error: No provider for t!
When I'm running
ionic serve -c
the firefox logs me
Unhandled Promise rejection: No provider for ConnectionBackend! ;
Zone: ; Task: Promise.then ; Value: Object { ...
app.module.ts looks like:
import { NgModule } from '#angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { Storage } from '#ionic/storage'; // special thing
import { Http } from '#angular/http';#
// other imports ....
#NgModule({
declarations: [
MyApp,
// PAGES
// MODALS
// CUSTOM COMPONENTS
// DIRECTIVES
],
imports: [
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
// PAGES
// MODALS
],
providers: [
Storage,
Http,
// SERVICES/PROVIDERS
]
})
export class AppModule {}
I'm guessing there is something wrong with a provider somewhere, but I just can't find a solution ...
System : ubuntu 16.04 / node v6.7.0 / npm v3.10.3
EDIT :
I started a new sidemenu project with
ionic start debugProject sidemenu --v2
I did this to to debug the providers by sequentially adding the providers of my original Project. It appears that, when I inject the first provider called "Config" in the constructor of app.components.ts
import { Component } from '#angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from 'ionic-native';
import { TabsPage } from '../pages/tabs/tabs';
// PROVIDERS -> providers
import { Config } from '../providers/config/config';
#Component({
template: `<ion-nav [root]="rootPage"></ion-nav>`
})
export class MyApp {
rootPage = TabsPage;
constructor(
platform : Platform,
config : Config <-------------(HERE !!! )
) { .....
I get the the Error message like before:
Unhandled Promise rejection: No provider for ConnectionBackend! ;
Zone: ; Task: Promise.then ; Value: Object { ...
config.ts
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
// providers
import { Http } from '#angular/http';
//import { DB } from '../db/db';
#Injectable()
export class Config {
public data : any;
constructor(
public http : Http,
//public db : DB
){
this.data = {};
}
loadDefault() {
this.data = {
..........................
// DATA OBJECT DEFINITIONS
...........................
};
return Promise.resolve("default settings applied");
}
loadSettingsFromDB(){
return Promise.resolve("no local settings from db for now");
}
// TODO: send settings to server
saveSettingsToDB(settings){
return Promise.resolve("cant save settings for now");
}
handleError(err) : void {
// error stacktrace gets returned on development mode
try{
err = err.json();
}catch(e){}
var msg = err.message || "LOADING ERROR";
if(err.error){
msg += err.error;
//Toast.showLongBottom(msg).subscribe();
}else{
//Toast.showShortBottom(msg).subscribe();
}
console.log("ERROR in config.ts");
console.log(msg);
console.log(err);
}
}
any ideas ? if not, any experience about opening a new issue on github ?
I found the solution. It seems like I can't/shouldn't use the Http module in the providers array in app.modules.ts (In beta11 this was not a problem). My Config module relied on Http, so after constructing Config the application Error (No provider for ConnectionBackend) fired ...

Failed: No provider for Config! (Icon -> Config) in Ionic2 unit tests with karma and jasmin

I'm writing unit tests for an ionic2 app but I get following error when the template contains some ionic elements
e.g.
<ion-icon > </ion-icon>
Failed: No provider for Config! (Icon -> Config)
Any idea?
I don't see how the previous solution works, because Config needs App, which needs Platform, which needs Keyboard which needs... maybe it is because you do not configure testing module asynchronously, which you should ;) simply try this:
import { IonicModule } from 'ionic-angular';
import { YourTestedComponent } from './pathto.component.ts'
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ IonicModule.forRoot(this) ], // this loads ionic deps
declarations: [ YourTestedComponent ],
});
}));
This should fix it
Best of luck
you're not allowed to use template in #app, try to use templateUrl: 'build/app.html' and create a app.html with your tags in it.
I run into the same problem. This worked for me:
import { async, ComponentFixture, TestBed } from "#angular/core/testing";
import { Config, IonicModule } from "ionic-angular";
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
],
imports: [
IonicModule,
],
providers: [
Config,
],
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
});