Why is the service not injected into the controller? - adonis.js

I'm trying out AdonisJS, but I'm stuck trying to get a service injected into a controller. No matter what I try, the hs constructor argument in VersionsController remains undefined.
I've also experimented with annotating the VersionController constructor with #inject(['#ioc:Service/HashService']), but with no luck. I'm not sure though if #inject is the right way for Adonis.js v5.
How do I properly inject a service class into a controller?
/providers/AppProvider.ts
import { ApplicationContract } from '#ioc:Adonis/Core/Application'
import HashService from 'App/Services/HashService';
export default class AppProvider {
protected app: ApplicationContract;
constructor(app: ApplicationContract) {
this.app = app;
}
public register() {
this.app.container.singleton('#ioc:Service/HashService', () => {
return new HashService();
});
}
public async boot() {
// IoC container is ready
}
public async ready() {
// App is ready
}
public async shutdown() {
// Cleanup, since app is going down
}
}
/app/Services/HashService.ts
'use strict'
export default class HashService {
async test() {}
}
module.exports = HashService;
app/Controllers/Http/VersionsController.ts
import { HttpContextContract } from '#ioc:Adonis/Core/HttpContext'
import { HashService } from '#ioc:Service/HashService'
export default class VersionsController {
protected hs: HashService
constructor(hs: HashService) {
this.hs = hs;
console.log("hashservice is " + hs);
}
public async get(ctx: HttpContextContract) {
return [
{
id: 1,
title: 'a'
}
]
}
public async put(ctx: HttpContextContract) {
return "PUT";
}
}
.adonisrc.json
{
"typescript": true,
"commands": [
"./commands",
"#adonisjs/core/build/commands/index.js",
"#adonisjs/repl/build/commands"
],
"exceptionHandlerNamespace": "App/Exceptions/Handler",
"aliases": {
"App": "app",
"Config": "config",
"Database": "database",
"Contracts": "contracts"
},
"preloads": [
"./start/routes",
"./start/kernel"
],
"providers": [
"./providers/AppProvider",
"#adonisjs/core"
],
"aceProviders": [
"#adonisjs/repl"
]
}

you can use Adonisjs/fold
simply use the #inject decorator in your services;
example;
import {HttpContextContract} from "#ioc:Adonis/Core/HttpContext";
import UserEditValidator from "App/Validators/UserEditValidator";
import UserRepository from "App/Repository/UserRepository";
import {inject} from "#adonisjs/fold";
#inject()
export default class UserService {
constructor(private userRepository: UserRepository) {
}
async update(ctx: HttpContextContract) {
//do your stuff
}
}
and then in your controller;
import {HttpContextContract} from "#ioc:Adonis/Core/HttpContext";
import UserService from "App/Service/UserService";
import {inject} from "#adonisjs/fold";
#inject()
export default class UserController {
constructor(private userService: UserService) {
}
async edit({ view }){
return view.render('user/edit', {title: 'Edit Profile'})
}
async update(ctx : HttpContextContract){
await this.userService.update(ctx);
return ctx.response.redirect().back()
}
}

Related

Mock two dependencies with NestJs and Jest

I'm struggling to mock two dependencies from my userService.
Here I have two dependencies: UserRepository and ProfileService
user.service.ts
import { Injectable, NotFoundException, BadRequestException } from '#nestjs/common';
import { UserRepository } from '../repository/user.repository';
import { userDTO } from '../typings/user.typings';
import { ProfileService } from './profile.service';
import { InjectRepository } from '#nestjs/typeorm'
#Injectable()
export class UserService {
constructor(
#InjectRepository(UserRepository)
private readonly repository: UserRepository,
private readonly profileService: ProfileService
) {
this.repository = repository;
}
async addUser(user: userDTO): Promise<userDTO> {
try {
const userCreated = await this.repository.addUser(user);
await this.profileService.createProfile(userCreated)
return userCreated
}
catch (error) {
console.log(error)
}
}
async getUser(field: string, value: string) {
const user = await this.repository.getUser(field, value);
return user
}
async deleteUser(field: string, value: string) {
await this.profileService.deleteProfile(userId)
return await this.repository.deleteUser(userId);
}
async getUserById(userId: string) {
return await this.repository.getUserById(userId);
}
async updateUser(user: userDTO, userId: string) {
return await this.repository.updateUser(user, userId);
}
}
user.service.spec.ts
import { Test, TestingModule } from "#nestjs/testing";
import { UserService } from "../../src/services/user.services";
import { ProfileService } from "../../src/services/profile.service";
import { UserRepository } from "../../src/repository/user.repository";
import { getRepositoryToken } from '#nestjs/typeorm';
describe('userService', () => {
let userService: UserService;
let mockRepository = {};
let mockProfileService = {};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(UserRepository),
useValue: mockRepository
},
{
provide: ProfileService,
useValue: mockProfileService
},
],
})
.compile();
userService = module.get<UserService>(UserService);
});
it('Should be defined', () => {
expect(userService).toBeDefined();
});
});
The test works fine when I only use UserRepository, but when I tried to mock the second repository it fails with this message
FAIL tests/services/user.service.spec.ts
● Test suite failed to run
Cannot find module 'src/models/profile.models' from '../src/repository/profile.repository.ts'
Can someone explain me how to mock 2 dependencies(a repository and a service).

Ionic 2: Error testing with Karma/Jasmine/TestBed

I'm new with Ionic2 and I was following this tutorial and a simple test like
describe('Dummy test', () => {
it('should do nothing', () => {
expect(true).toBeTruthy();
expect(1 + 1).toBe(2);
});
});
works fine, but for some reason I keep getting this error when I try to follow the rest of the tutorial.
Component: Root Component
✖ initialises with a root page of LoginPage
Firefox 45.0.0 (Linux 0.0.0)
TypeError: win is undefined in src/test.ts (line 937)
My src/test.ts is the same as the tutorial and it doesn't have any win in it. My app.spec.ts is this
import { TestBed, ComponentFixture, async } from '#angular/core/testing';
import { IonicModule } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { UserData } from '../providers/user-data';
import { LoginPage } from '../pages/login/login';
import { Platform } from 'ionic-angular';
import { MyApp } from './app.component';
import { LoginPage } from '../pages/login/login';
let comp: MyApp;
let fixture: ComponentFixture<MyApp>;
describe('Component: Root Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyApp],
providers: [
StatusBar,
SplashScreen,
UserData,
Platform
],
imports: [
IonicModule.forRoot(MyApp)
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyApp);
comp = fixture.componentInstance;
});
afterEach(() => {
fixture.destroy();
comp = null;
});
it('initialises with a root page of LoginPage', () => {
expect(comp['rootPage']).toBe(LoginPage);
});
});
And my app.component.ts is this
import { Component } from '#angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import { MenuSidePage } from '../pages/menu-side/menu-side';
import { LoginPage } from '../pages/login/login';
import { UserData } from '../providers/user-data';
#Component({
template: `<ion-nav #nav [root]="rootPage"></ion-nav>`
})
export class MyApp {
rootPage: any;
constructor(
public platform: Platform,
public statusBar: StatusBar,
public splashScreen: SplashScreen,
private userData: UserData,
) {
platform
.ready()
.then(() => {
//First - check if user is logged
if(this.userData.currentUser) {
this.rootPage = MenuSidePage;
} else {
this.rootPage = LoginPage;
}
statusBar.styleDefault();
splashScreen.hide();
});
}
}
I don't have yet the solution, but you shouldn't use compileComponents() 'cause you are using a template and not a templateUrl like said in this tutorial :
"We need to use compileComponents when we need to asynchronously compile a component, such as one that has an external template (one that is loaded through templateUrl and isn’t inlined with template). This is why the beforeEach block that this code runs in uses an async parameter – it sets up an asynchronous test zone for the compileComponents to run inside."
Hope it's a kind of helping :)
The win() function come from the Plaftorm, you have to mock it as follow :
export class PlatformMock {
public ready(): Promise<string> {
return new Promise((resolve) => {
resolve('READY');
});
}
public getQueryParam() {
return true;
}
public registerBackButtonAction(fn: Function, priority?: number): Function {
return (() => true);
}
public hasFocus(ele: HTMLElement): boolean {
return true;
}
public doc(): HTMLDocument {
return document;
}
public is(): boolean {
return true;
}
public getElementComputedStyle(container: any): any {
return {
paddingLeft: '10',
paddingTop: '10',
paddingRight: '10',
paddingBottom: '10',
};
}
public onResize(callback: any) {
return callback;
}
public registerListener(ele: any, eventName: string, callback: any): Function {
return (() => true);
}
public win(): Window {
return window;
}
public raf(callback: any): number {
return 1;
}
public timeout(callback: any, timer: number): any {
return setTimeout(callback, timer);
}
public cancelTimeout(id: any) {
// do nothing
}
public getActiveElement(): any {
return document['activeElement'];
}
}
Here is the link to see a project for real integration of this mock class.
Hope it helps :)

Ionic 2: How to call Provider function in Controller class

I have created new Cordova Plugin only on my machine. Then I added it to my project. It is working fine when I call that plugin. Now, I tried to make a structured caller for my plugin. I created a Provider for it, but the problem is I don't know how to call my plugin function from my Controller class. Below is my sample code.
Provider: my-service.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
declare let myPlugin: any;
#Injectable()
export class MyService {
constructor(public http: Http) {
console.log('Hello MyService Provider');
}
public myFunction() {
myPlugin.myPluginFunction(
(data) => {
return data;
},
(err) => {
return err;
});
}
}
Pages: my-page.ts
import { Component } from '#angular/core';
import { NavController, ViewController } from 'ionic-angular';
import { MyService } from '../../providers/my-service';
#Component({
selector: 'page-my-page-ionic',
templateUrl: 'hello-ionic.html'
})
export class MyPage {
constructor(private viewCtrl: ViewController, private myService: MyService) {}
ionViewWillEnter() {
//I tried to call like this
this.myService.myFunction().subscribe(
data => {
alert("success");
},
error => {
alert("error");
});
}
}
It returns me this error - Property 'subscribe' does not exist on type 'void'. I don't know how to call that function, since my provider returns me success or error.
I think since your myFunction() does not return any observable you cannot subscribe to it. It just returns data directly.
You can use it like this in this case:
var data = this.myService.myFunction();
console.log("Data from plugin is :", data);
If you want to use it as an Observable, return a new observable like this:
public myFunction() {
return Observable.create(observer => {
myPlugin.myPluginFunction(
(data) => {
observer.next(data);
},
(err) => {
observer.next(data);
});
},
(err) => {
observer.error(err);
});
}

Ionic 2 ViewController unit testing

mIve got the following unit tests that tests a Component I have written in Ionic 2. The unit tests gives an error from one of the Ionic libraries, I assume that I am not mocking it out properly or as such
import { ComponentFixture, async } from '#angular/core/testing';
import { TestUtils } from '../../test';
import {} from 'jasmine';
import { LocationSearchModal } from './LocationSearchModal';
import { LocationService } from '../../services/LocationService';
import { PouchDbService } from '../../services/common/PouchDbService';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { TestBed } from '#angular/core/testing';
import { App, MenuController, NavController, Platform, Config, Keyboard, Form, IonicModule, ViewController, GestureController, NavParams } from 'ionic-angular';
import { ConfigMock } from '../../mocks';
import { TranslateModule } from 'ng2-translate';
import { LoadingController } from 'ionic-angular';
let fixture: ComponentFixture<LocationSearchModal> = null;
let instance: any = null;
describe('LocationSearchModal', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
LocationSearchModal
],
providers: [
App, Platform, Form, Keyboard, MenuController, NavController, GestureController, LocationService, LoadingController,
{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
{ provide: NavParams, useClass: class { NavParams = jasmine.createSpy("navParams"); } },
{ provide: PouchDbService, useClass: class { PouchDbService = jasmine.createSpy("pouchDbService"); } },
{provide: Config, useClass: ConfigMock}
],
imports: [
FormsModule,
IonicModule,
ReactiveFormsModule,
TranslateModule.forRoot(),
],
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(LocationSearchModal);
instance = fixture.debugElement.componentInstance;
fixture.autoDetectChanges(true);
});
}));
afterEach(() => {
fixture.destroy();
});
it('loads', () => {
expect(fixture).not.toBeNull();
expect(instance).not.toBeNull();
})
})
This is the relevant excerpt which uses the ViewController from the component that is being tested.
this.locationService.getLocationById(this.selectedLocation)
.subscribe((location: any) => {
this.viewController.dismiss(location.doc)
});
The test fails and I get the following stack trace
Chrome 53.0.2785 (Linux 0.0.0)
TypeError: viewCtrl._setHeader is not a function
at new Header (webpack:///home/milinda/workspaces/eclipse/inspection/addedinspection/Inspection-Upgrade/~/ionic-angular/components/toolbar/toolbar.js:14:0 <- src/test.ts:11833:30)
at new Wrapper_Header (/IonicModule/Header/wrapper.ngfactory.js:7:18)
This is related to the ViewController line which I have created a jasmine spy for
{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
After having a look at the code base I found the _setHeader method in here
https://github.com/driftyco/ionic/blob/6b3e2ed447340cdd35c328c96aa7cfa5f34eb214/src/navigation/view-controller.ts#L364
I also tried writing a custom provider but got the same error as well. Any idea on what is the correct method of testing the ViewController.
Additionally sometimes after resolving the ViewController issue an issue may occur from NavParams perhaps
Building on Marky Sparky's answer. As of ionic 3+:
export class ViewControllerMock{
readReady = {
subscribe(){
}
};
writeReady = {
subscribe(){
}
};
dismiss(){
console.log('View Controller Dismiss Called');
}
_setHeader(){
}
_setNavbar(){
}
_setIONContent(){
}
_setIONContentRef(){
}
}
Working on version:
Cordova CLI: 6.5.0
Ionic Framework Version: 3.0.1
Ionic CLI Version: 3.0.0-beta7
ios-deploy version: 1.9.1
ios-sim version: Not installed
OS: macOS Sierra
Node Version: v7.8.0
Xcode version: Xcode 8.3.2 Build version 8E2002
I had the same issue when referring to ViewController in unit tests. I just solved it.
Create a mock like this
class ViewControllerMock {
public _setHeader(): any {
return {}
};
public _setIONContent(): any {
return {}
};
public _setIONContentRef(): any {
return {}
};
}
Then add it to your providers in the call to TestBed.configureTestingModule like this:
TestBed.configureTestingModule({
declarations: [
...components,
OrdinalPipe,
IgnoreNulls
],
providers: [
NavController,
ChartsService, FundsService, Utils, BlogService
, Payment, PlanHelper, Storage, PalIdle, SimpleExpiry, ContentService, PlansService,
App, Platform, Form, Keyboard, MenuController,
{ provide: ModalController, useClass: ModalControllerMock },
{ provide: ViewController, useClass: ViewControllerMock },
{ provide: Config, useClass: ConfigMock }
],
imports: [
FormsModule,
IonicModule,
ReactiveFormsModule,
],
})
This worked for me when I had the viewCtrl._setHeader is not a function error earlier today. Hope it helps.
Accepted answer did not work for ionic version 3.9.2, however following fixed the issue:
export class ViewControllerMock {
public readReady: any = {
emit(): void {
},
subscribe(): any {
}
};
public writeReady: any = {
emit(): void {
},
subscribe(): any {
}
};
public contentRef(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public didEnter(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public didLeave(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public onDidDismiss(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public onWillDismiss(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public willEnter(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public willLeave(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public willUnload(): any {
return new Promise(function (resolve: Function): void {
resolve();
});
}
public dismiss(): any {
return true;
}
public enableBack(): any {
return true;
}
public getContent(): any {
return true;
}
public hasNavbar(): any {
return true;
}
public index(): any {
return true;
}
public isFirst(): any {
return true;
}
public isLast(): any {
return true;
}
public pageRef(): any {
return true;
}
public setBackButtonText(): any {
return true;
}
public showBackButton(): any {
return true;
}
public _setHeader(): any {
return true;
}
public _setIONContent(): any {
return true;
}
public _setIONContentRef(): any {
return true;
}
public _setNavbar(): any {
return true;
}
public _setContent(): any {
return true;
}
public _setContentRef(): any {
return true;
}
public _setFooter(): any {
return true;
}
}
Ionic Info
cli packages:
#ionic/cli-plugin-proxy : 1.5.6
#ionic/cli-utils : 1.14.0
ionic (Ionic CLI) : 3.14.0
local packages:
#ionic/app-scripts : 3.1.0
Ionic Framework : ionic-angular 3.9.2
In case someone doesn't notice the comments on James Macmillan's answer:
#eesdil wrote:
From beenotung, there is built in mocks in ionic also: import
{mockApp, mockConfig, mockPlatform, mockView} from
"ionic-angular/util/mock-providers"; and use it like {provide:
ViewController, useValue: mockView()}, – Sep 26 '17 at 9:40
This is the solution that worked for me.
1. Create Jasmine spy of ViewController.
let viewCtrlSpy = jasmine.createSpyObj('ViewController',
['data', 'readReady', 'writeReady', 'dismiss', '_setHeader', '_setNavbar', '_setIONContent', '_setIONContentRef']);
2. Use viewCtrlSpy spy in providers array as follow:
providers: [
.......
{
provide: ViewController,
useValue: viewCtrlSpy
}
..........
Spying is more efficient in this case than mocking.
add
{ provide: ViewController, useClass: class { ViewController = jasmine.createSpy("viewController"); } },
to provides

How to mock AngularFire 2 service in unit test?

I'm trying to set up unit tests for a sample Angular 2 app using AngularFire 2 auth, the component is fairly simple:
import { Component } from '#angular/core';
import { AngularFire, AuthProviders } from 'angularfire2';
#Component({
moduleId: module.id,
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css']
})
export class AppComponent {
isLoggedIn: boolean;
constructor(public af: AngularFire) {
this.af.auth.subscribe(auth => {
if (auth) {
this.isLoggedIn = true;
} else {
this.isLoggedIn = false;
}
});
}
loginWithFacebook() {
this.af.auth.login({
provider: AuthProviders.Facebook
});
}
logout() {
this.af.auth.logout();
}
}
All I'm doing is wrapping around the login and logout methods in AngularFire so I was thinking about using a mock to check if the methods were called but I'm not sure where to start, I tried doing the following in my spec file:
import { provide } from '#angular/core';
import { AngularFire } from 'angularfire2';
import {
beforeEach, beforeEachProviders,
describe, xdescribe,
expect, it, xit,
async, inject
} from '#angular/core/testing';
import { AppComponent } from './app.component';
spyOn(AngularFire, 'auth');
beforeEachProviders(() => [
AppComponent,
AngularFire
]);
describe('App Component', () => {
it('should create the app',
inject([AppComponent], (app: AppComponent) => {
expect(app).toBeTruthy();
})
);
it('should log user in',
inject([AppComponent], (app: AppComponent) => {
expect(app.fb.auth.login).toHaveBeenCalled();
})
);
it('should log user out',
inject([AppComponent], (app: AppComponent) => {
expect(app.fb.auth.logout).toHaveBeenCalled();
})
);
});
However I'm not sure how to mock the login and logout methods since they're part of the auth property, is there a way to mock auth and also the returning login and logout methods?
In this snippet:
beforeEach(() => addProviders([
AppComponent,
AngularFire
]);
You set (or override) the providers that will be used in your test.
That being said, you can create a different class, a mock if you will, and, using the { provide: originalClass, useClass: fakeClass } notation, provide it instead of the AngularFire actual class.
Something like this:
class AngularFireAuthMock extends AngularFireAuth { // added this class
public login() { ... }
public logout() { ... }
}
class AngularFireMock extends AngularFire { // added this class
public auth: AngularFireAuthMock;
}
beforeEach(() => addProviders([
AppComponent,
{ provide: AngularFire, useClass: AngularFireMock } // changed this line
]);
And the AngularFires in your tests will be AngularFireMocks.
hope it is not off the topic, but the easiest solution I have found how to mock the FirebaseDatabase.
var object = function() {
var obj = { valueChanges() {
return of({data:'data'});
}
}
return obj;
}
providers: [..., { provide : AngularFireDatabase,
useValue: {object : object }} ]
instead of data:'data' you can mock whatever data you need. The functions can be modified as you wish.
Similar to #jan, I made a mock using some utility functions:
import {AngularFireAuth} from '#angular/fire/auth';
import {AngularFireDatabase} from '#angular/fire/database';
import {auth} from 'firebase/app';
import { Observable, of, Subscription } from 'rxjs';
/**
* Mocks the Firebase auth by automatically logging in.
*/
export const AngularFireAuthMock = jasmine.createSpy('signInWithEmailAndPassword')
.and.returnValue(Promise.resolve({uid: 'fakeuser'}));
/**
* Mocks an AngularFireDatabase that always returns the given data for any path.
*/
export function mockAngularFireDatabase(data): AngularFireDatabase {
return {
object: (path: string): any => {
return {
valueChanges() {
return of(data);
}
}
}
} as AngularFireDatabase;
}
and then you can use them in your spec like this:
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TweakComponent ],
imports: [ MatDialogModule, RouterTestingModule ],
providers: [
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: AngularFireDatabase, useValue: mockAngularFireDatabase({testdata:'hi'})},
{ provide: AngularFireAuth, useValue: AngularFireAuthMock}
],
})
.compileComponents();
});