Dependency injection with utility classes - ember.js

In EmberJS, if I have a utility class I've injected into the container via an initializer, how can I inject dependencies into that utility class?
For example, one could inject a messages class with this initializer:
import Messages from 'app/utils/messages';
var injectMessagesInitializer = {
name: 'injectMessages',
before: 'authentication',
initialize: function (container, application) {
application.register('messages:main', Messages, {initialize: false, singleton: true});
application.inject('controller', 'messages', 'messages:main');
application.inject('component', 'messages', 'messages:main');
application.inject('router', 'messages', 'messages:main');
application.inject('route', 'messages', 'messages:main');
}
};
export default injectMessagesInitializer;
Now, I'd like to be able to inject a Notifier class into that Messages utility. Can I do something like the following?
import Notifier from 'notifier';
var injectNotifierIntoMessagesInitializer = {
name: 'injectNotifierIntoMessages',
after: 'injectMessages',
initialize: function (container, application) {
application.inject('messages', 'notifier', 'notifier:main');
}
};
export default injectNotifierIntoMessagesInitializer;
Inside the utility class:
var Messages = Ember.Object.extend({
showError: function (message, options) {
this.notifier.error(message, options);
},
showErrors: function (errors, options) {
options = options || {};
for (var i = 0; i < errors.length; i += 1) {
this.showError(errors[i].message || errors[i], options);
}
},
...
});
A couple things of note:
The notifier class is a simple notifier, something like ember-cli-growl that does one thing well, throws single notifications.
The messages class is a wrapper for the notifier, adding convenience methods like showError, showErrors, showInfo, showWarn. Essentially, it abstracts the throwing of notifications so we can pivot to a different type or class of notifications in the future.

Related

Angular2 - Call function from a tested component

I'm currently writing unit tests for Angular2 with Karma and Jasmine, but I'm pretty new in the unit testing and I'm facing some difficulties. When it comes to testing hardcoded properties or properties that don't involve async functions, it's all okay, but I need to be able to call the component's functions in order for some variables to get their values. What I'm doing is the following:
My component:
export class LoginComponent implements OnInit {
formLoginId: string;
loginUrl: string;
email: string;
password: string;
constructor(private googleAuthService: GoogleAuthService,
private authService: AuthenticationService,
private validationService: ValidationService,
private router: Router,
private titleService: Title) {
this.titleService.setTitle("Login");
this.formLoginId = "#form-login";
}
ngOnInit() {
this.googleAuthService.getLink((response) => {
this.loginUrl= response.json().url;
});
}
login() {
if (this.validationService.isValid(this.formLoginId)) {
this.authService.login(this.email, this.password);
}
}
Now I want to write a unit test which can check if the loginUrl has taken any value. My test is bellow:
describe('Login Component', ()=> {
let component:LoginComponent;
let fixture:any;
beforeEach(async(()=> {
TestBed.configureTestingModule({
//declarations,imports and providers
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
}); /some-other-tests/
it('should have login url', fakeAsync(()=> {
component.ngOnInit();
tick(1000);
expect(component.loginUrl).toBeDefined();
}));
});
But it seems that its not working. I'm still getting undefined for the mentioned variable. How can I call a method from a component and check the variables after its result?
Thanks!
In this case, you need to mock the GoogleAuthService to return with some information, as otherwise that getLink never resolves.
You can specify a mock provider for the GoogleAuthService and have it return an observable that's already resolved.

How to unit test an ember controller

I have a single action defined in an ember controller that calls 2 separate functions that are part of the controller. I'd like to mock out those functions in a unit test in order to confirm if the action method called the correct function.
My controller looks like this:
export default Ember.Controller.extend({
functionA() {
return;
},
functionB() {
return;
},
actions: {
actionMethod(param) {
if(param) {
return this.functionA();
}
else {
return this.functionB();
}
}
}
});
In practice, the controller works, however in the unit test, functionA and functionB are both undefined. I tried to log this to the console, but can't find where functionA and functionB are, so I'm unable to properly mock them. I expected them to be in the top level of the object next to actions, but instead I only found _actions with actionMethod properly defined.
My unit test looks like below
const functionA = function() { return; }
const functionB = function() { return; }
test('it can do something', function(assert) {
let controller = this.subject();
// I don't want the real functions to run
controller.set('functionA', functionA);
controller.set('functionB', functionB);
controller.send('actionMethod', '');
// raises TypeError: this.functionA is not a function
// this doesn't work etiher
// controller.functionB = functionB;
// controller.functionA = functionA;
// controller.actions.actionMethod();
}
Does anyone have any ideas on how I can replace those functions in the testing environment? Or perhaps, is there a better way to test this functionality or set up my controller?
edit
typo: this.subject to this.subject()
To replace the functions of the controller in the unit test, you can pass parameter to the this.subject() function:
let controller = this.subject({
functionA(){
//this function overriddes functionA
},
functionB(){
//this function overriddes functionB
},
});
Look at the sample twiddle.
This method is especially useful for replacing the injected service of the controllers.
Introduce corresponding property you are dealing with, let us say name property,
So your controllers would be looking like this,
import Ember from 'ember';
export default Ember.Controller.extend({
name:'',
functionA() {
this.set('name','A');
},
functionB() {
this.set('name','B');
},
actions: {
actionMethod(param) {
if(param) {
return this.functionA();
}
else {
return this.functionB();
}
}
}
});
And you can test for the name property value after calling actionMethod.
test(" testing functionA has been called or not", function(assert){
let controller = this.subject();
controller.send('actionMethod',true);
//If you would like to call functionA just say controller.functionA()
assert.strictEqual(controller.get('name'),'A',' name property has A if actionMethod arguments true');
controller.send('actionMethod',false);
assert.strictEqual(controller.get('name'),'B',' name property has B actionMethod arguments false');
});

Angular 2 TestBed, Mocking Methods without Dependency Injection

Using TestBed, we are able to create mock classes for classes that are available with dependency injection. For example, MyButtonClass has access to ElementRef and MyService since they are implemented with dependency injection, and so we can override them. The problem I have is that, to write a Jasmine test, I have to create mock classes to override methods of classes that are not accessed with dependency injection.
In this case, ScriptLoader.load will load ThirdPartyCheckout in the global space. This means, it might not be available when Jasmine reads what is inside the subscribe operator. For this reason, I would like to mock the former first and then the latter after. Or maybe there is a different way to get around this.
It would be great if someone can suggest a way to create mock classes to override the ScriptLoader.load method and ThirdPartyCheckout.configure method.
The directive to be tested:
#Directive({
selector: '[myButton]'
})
export class MyButtonClass implements AfterViewInit {
private myKey: string;
constructor(private _el: ElementRef, private myService: MyService) {}
ngAfterViewInit() {
this.myService.getKey()
.then((myKey: string) => {
this.myKey = myKey;
ScriptLoader.load('https://thirdpartyurl.js', this._el.nativeElement)
.subscribe(
data => {
this.handeler = ThirdPartyCheckout.configure(<any>{
key: this.myKey
// etc
// and some methods go here
});
},
error => {
console.log(error);
}
);
});
}
}
Here is the test code:
#Component({
selector: 'test-cmp',
template: ''
})
class TestComponent {}
class mockMyService {
getKey() {
return Promise.resolve('this is a key in real code');
}
}
describe('myButton', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent, MyButtonClass],
providers: [
{provide: MyService, useClass: mockMyService}
]
});
});
describe('ngAfterViewInit', fakeAsync(() => {
const template = '<div><div myButton></div></div>';
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
tick();
}));
});
Functions being first-class citizens, you can just assign a new function to it
let originalFn;
beforeEach(() => {
originalFn = ScriptLoader.load;
});
afterEach(() => {
ScriptLoader.load = originalFn;
});
it('...', fakeAsync(() => {
ScriptLoader.load = (url, el: Element): Observable<string> => {
return Observable.of('HelloSquirrel');
};
...
}));
Other than this, you might want to just consider using DI. One of the main reasons for using DI is for better testability. For the ScriptLoader just make the method a non static method, and for the third party lib just create as abstraction service layer for it.

this.transitionToRoute not working in my controller Ember

I am using a controller to read the value selected on a drop down menu, take in parameters of some input fields and then save the record. It creates the record and takes in the information just fine. My problem lies when I try to transition to another page at the end of the action. I keep getting the error: Cannot read property 'transitionToRoute' of undefined
I am completely stumped. Any ideas?
Here is my controller code:
var teamId;
export default Ember.Controller.extend({
auth: Ember.inject.service(),
actions: {
onSelectEntityType: function(value) {
console.log(value);
teamId = value;
return value;
},
createProcess: function(processName, processDescription) {
var currentID = this.get('auth').getCurrentUser();
let team = this.get('store').peekRecord('team', teamId);
let user = this.get('store').peekRecord('user', currentID);
let process = this.get('store').createRecord('process', {
team: team,
user: user,
name: processName,
description: processDescription
});
process.save().then(function () {
this.transitionToRoute('teams', teamId);
});
}
}
});
Here is the corresponding route:
export default Ember.Route.extend({
auth: Ember.inject.service(),
model: function() {
var currentID = this.get('auth').getCurrentUser();
return this.store.find('user', currentID);
}
});
You should have clear understanding about this keyword in Javascript. The keyword this only depends on how the function was called, not how/when/where it was defined.
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
Have a look at the MDN documentation to learn more.
You can cache the this in normal variable this and then access inside the call back.
var self = this;
process.save().then(function () {
self.transitionToRoute('teams', teamId);
});
ECMASCript 6 introduced arrow functions whose this is lexically scoped. Here, this is looked up in scope just like a normal variable.
process.save().then(() => {
this.transitionToRoute('teams', teamId);
});

Accessing an injected object from inside of a class in EmberJS

I want to re-open a class and define a new 'class' function. Inside this new function, I want to have access to an injected property. Is there a way to do this?
Injecting dependency
export default {
name: 'userManager',
initialize: function(container, application){
var userManager = Ember.Object.extend({
//Some stuff
});
application.register('manager:user', userManager);
application.inject('route', 'userManager', 'manager:user');
application.inject('model', 'userManager', 'manager:user');
application.inject('controller', 'userManager', 'manager:user');
}
};
Re-opening a class to add a class method
import Ember from 'ember';
var Person = Ember.Object.extend({});
Person.reopenClass({
//how do I access an injected object here?
})
export default Invoice;
Whats wrong with?
Person.reopenClass({
someMethod: function() {
var userManager = this.get('userManager');
}
})