Ember Integration test with ember-data models - ember.js

How can I can I test my components that get an ember-data model passed into it as props?
For example:
{{#queue/review/moderatable-text model=activity property="info_name" handleModeration="handleModeration"}}
{{pro-form-textfield value=activity.info_name}}
{{/queue/review/moderatable-text}}
where activity is a model instance.
How do I setup my integration test to pass in activity and to test it where component can save the model?
I tried to stub out as if it's pure ember objects:
test('it sets approved', function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL +
this.set('property', 'info_title');
this.set('model', Ember.Object.create({counterpart: Ember.Object.create()}))
// Template block usage:" + EOL +
this.render(hbs`
{{#queue/review/moderatable-text property=property}}
{{pro-form-textfield value=value}}
{{/queue/review/moderatable-text}}
`);
this.$('.approve-button').click();
assert.ok(this.get('approved'));
});
but then I'd have to create my own save() methods and others.
Thoughts?

but then I'd have to create my own save() methods and others.
It's good, you can go with that approach. In my opinion, it's advantage that you can create your own methods. You can place assertions in them. So, for example, if you expect component to call save method of model you can place assertion in save method body:
Ember.Object.create({
counterpart: Ember.Object.create(),
save() {
assert.ok('save method called');
}
});
This gives you better control over testing behavior in tests.

Use this.inject.service('store'); in your the beforeEach() of the moduleFor[...]() object:
beforeEach: function() {
this.inject.service('store');
this.user = this.store.createRecord('user', { name: "Frank" });
}
Then you can use this.user in your tests. Also explained here:
https://guides.emberjs.com/release/tutorial/service/

Related

ember unit test route - this is undefined

I try to write my first unit test for a route.
project/files
actions: {
afterSave(savedFile){
// ... some code
let controller = this.controllerFor('project.files');
// ...
}
}
the test:
test('save file', function(assert) {
let route = this.subject();
console.log(route);
let project;
Ember.run(() => {
project = route.get('store').createRecord('project', {
id: '1',
name: 'test'
});
let afterSave = route.get('actions.afterSave');
afterSave(project);
});
assert.ok(true);
})
The problem that I am getting TypeError: Cannot read property 'controllerFor' of undefined.
It looks like this is undefined.
If you have a look at Testing Routes section from Ember Guides, you can see its suggestion is to separate the action and the function.
I can suggest it.
It uses send method of routes, such as: route.send('afterSave');
But if you only want to make run your code, call afterSave action from your test code such as: afterSave.bind(route)(project);. Ref: bind function (I don't suggest this. Also I don't suggest you to retrieve action such as: route.get('actions.afterSave'))

Mocking component function for the purposes of component integration test in EmberJS

Let'a assume a simple EmberJS component:
//my-app/components/my-component.js
export default Ember.Component.extend({
classNames: ['cursor-pointer'],
doSth(){
// whatever, state of the component does not change
},
clickListener: Ember.on('click', function(){
this.doSth();
})
});
Now I would like to use integration tests to find out whether clicking the component reaches (delegates to) the doSth() method.
moduleForComponent('my-component', 'Integration | Component | stores parser previewer', {
integration: true
});
test('should call doSth() on click', function (assert) {
/* Line 3 - the place I tried to set mocked doSth() up */
this.render(hbs`{{my-component}}`);
this.$('.cursor-pointer').click(); // triggers clickListener() correctly
});
The problem is that I cannot substitute method doSth() with my mock.
Thus I never run into assert.ok() statement.
I have tried to insert following statements onto the line 3 in the test:
// idea 1
this.set('doSth', function(){
assert.ok(true);
});
// idea 2
this.doSth = function(){
assert.ok(true);
};
None of the 2 approaches (idea 1, idea 2) worked. this.subject() is also unavailable since it's an integration test, not a unit test.
Update:
Imagine doSth() be a function like the openUrlInANewTab() shown below that does not influence the component's state, nor the associated controller's nor route's state.
Example:
//my-app/components/my-component.js
...
import openUrlInANewTab from 'my-app/utils/new-tab-opener';
...
export default Ember.Component.extend({
...
doSth(url){
openUrlInANewTab(url);
}
while
// my-app/utils/new-tab-opener.js
export default function openUrlInANewTab(){
anchor = document.createElement("a");
...
anchor.click();
...
}
You can move doSth to service custom-service that you inject to your my-component. Then in test you can EASILY test this by injecting fake service.
tests/integration/components/my-component-test.js:
test('should call doSth() on click', function(assert) {
const customStub = Ember.Service.extend({
doSth() {
// whatever, state of the component does not change
console.log('Stubbed doSth fired');
assert.ok(true, 'Stubbed doSth fired');
}
});
this.register('service:custom-service', customStub);
this.inject.service('custom-service', { as: 'customService' });
this.render(hbs`{{my-component}}`);
this.$('.cursor-pointer').click(); // triggers clickListener() correctly
});
components/my-component.js:
import Ember from 'ember';
export default Ember.Component.extend({
customService: Ember.inject.service('custom-service'),
classNames: ['cursor-pointer'],
clickListener: Ember.on('click', function(){
this.get('customService').doSth();
console.log('Click listener');
})
});
Working demo.
Ember Guides.
As an alternative to what Daniel had proposed you might consider using a closure action in case of handling click within your component. What I mean is instead of delegating to one of your methods (what you had done and could not test) or to a service (what Daniel suggested) just trigger an action passed to your component. This way also provides you the opportunity to mock in your test. Moreover, this approach results in a more reactive component; since you inject the behavior to your component from the owner at runtime; which enables you to reuse your custom component within different contexts (different parent components/templates) easily. See the twiddle for an example of what I mean.

Injection of service into Ember tests

I have read and followed EmberJS Service Injection for Unit Tests (Ember QUnit) but I'm still not able to figure where the problem is.
I would like to test if my authentication is working as expected. I have written authenticator for ember-simple-auth and session is injected into route. Code itself is working without any issues.
export default Ember.Route.extend({
authManager: Ember.inject.service('session'),
...
(in actions):
this.get('authManager').invalidate()
Now, I want to create a test which will test if my authentication is working as I expect. So I wish to use authManager directly.
moduleFor('route:index', 'Unit | Route | xyz', {
needs: ['service:session']
});
test('2', function(assert) {
let route = this.subject();
let s = route.get('authManager');
When I print the content of 's', I get ''. If I change this to something else, then response is undefined as can be expected. Problem is when I want to obtain property 'isAuthenticated' or run 'invalidate()'. In these cases I got 'undefined'. What am I doing wrong?
As of Ember 2.13, the correct solution to this is to use this.register:
test('my test', function(assert) {
this.register('service:session', Ember.Service.extend({
/* mock code */
}));
let subject = this.subject();
// test code goes here...
}
In a unit test, we prefer to use mock objects instead of services. In integration tests, we may use real services instead of mocks.
To mock a service, in a unit test:
var stubMyService = Ember.Object.extend({
//This is a mock object, write a code to test component/route!
invalidate: function() {
return 'invalidateCalled';
},
isAuthenticated: function(){
return true;
}
});
To inject this mock object to your component/route use this.subject() creation as following:
test('2', function(assert){
var component = this.subject({
authManager: stubMyService.create()
});
...
});

Emberjs Testing a Component's action delegate

I'm trying to test a simple component that uses action delegation. I want to test that a certain action is sent to the actionDelegate of a component. I was thinking of using Sinon to check that the action was being sent, but I can't work out how to replicate the structure that Ember uses to send its actions to its delegates/targets.
What structure would my sinon "spy delegate" object take in order for me to check that the component is delegating the event using "send" when the user clicks on a button?
I've created an example of the kind of thing I want to test at http://jsfiddle.net/L3M4T/ but it haven't a testing harness around it (it's kind of a big job to put a testing harness around a component just for a simple js fiddle - in fact it was quite a bit of a job to get this component into the shape I wanted to explain this problem).
Here's my component:
App.AppProfileComponent = Ember.Component.extend({
actionDelegate: null,
actions: {
hello: function(person) {
if(!Ember.isEmpty(this.get('actionDelegate'))) {
this.get('actionDelegate').send('hello', person);
}
}
}
});
And my inital try that didn't work was simply to write a test that had this fragment in it (using sinon & qunit):
visit("/").click("button").then(function() {
equal(actionDelegateSpy.called, true, "Action delegate should be called when button pressed");
});
I think it's pretty obvious why that didn't work, but since I've tried the following, which also didn't work:
var actionDelegateSpy = sinon.spy();
var actionDelegate = Ember.ObjectController.extend({
actions: {
"hello" : actionDelegateSpy
}
}).create();
then testing by passing in the actionDelegate defined above as the actionDelegate on the component for a test.
I fixed my own issue... Silly me:
test("delegates its hello action to actionDelegate", function() {
var actionDelegateSpy;
Ember.run(function() {
actionDelegateSpy = sinon.spy();
var actionDelegate = Ember.ObjectController.extend({
actions: {
"hello" : actionDelegateSpy
}
}).create();
controller.set('actionDelegate', actionDelegate);
});
visit("/").click("button")
.then(function() {
equal(actionDelegateSpy.called, true, "Action delegate should be called when hello button pressed");
});
});

Observer not firing on created object

I am trying to use observers to observe a change on my model after XHR. This is because the earlier approach of extending a fn and calling super is not allowed any more.
Running into this weird issue where my observer doesn't fire:
App = Ember.Application.create({
ready: function () {
console.log('Ember Application ready');
this.topCampaignsController = Ember.ArrayController.create({
content: null
});
App.TopCampaignsModel.create({
// Calling super is no longer allowed in object instances
//success: function () {
// this._super();
// App.topCampaignsController.set('content', this.get('data'));
//},
onDataChange: function () {
console.log('data property on the object changed');
App.topCampaignsController.set('content', this.get('data'));
}.observes('data')
});
}
});
App.TopCampaignsModel = Ember.Object.extend({
data: null,
// this will be actually called from an XHR request
success: function () {
this.set('data', [5,10]);
},
init: function () {
console.log('TopCampaignsModel created');
this.success();
console.log(this.get('data'));
}
});
Jsfiddle here: http://jsfiddle.net/gdXfN/26/
Not sure why the console doesn't log "data property on the object changed". Open to alternative approaches on how I can override the 'success' fn in my instance.
After this commit in December last year, it is no longer possible to set observers during object creation. This resulted in a huge performance win.
To set observers on create you need to use:
var Object = Object.createWithMixins({
changed: function() {
}.observes('data')
});
Here's a fiddle demonstrating this.
The API documentation should be updated accordingly, something I will do later on.
However, I don't advise you to do that, but instead set observers during object definition. The same result can be achieved: http://jsfiddle.net/teddyzeenny/gdXfN/32/
That said, there are two things you are doing that go against Ember concepts:
You should not create controller instances yourself, you should let Ember create them for you:
App.TopCampaignsController = Em.Controller.extend({ content: null });
When the App is initialized, Ember will generate the controller for you.
Models should not be aware of controller existence. Controllers should access models not the other way round.
Models and Controllers will interact together through routes.
For the last two points, you can watch the tutorial at http://emberjs.com/guides/ to see how the Application, Controllers, Models, and Routes should interact. Since you're not using
Ember Data, just ignore DS.Model and imagine an Ember.Object instead. The tutorial can give you a pretty good overview of how objects should interact.