How to create unit tests for Ember.js adapter/serializers? - unit-testing

I'm building an Ember CLI app (v0.2.3), and I have some unit tests that have been generated for me for the adapters and serializer in my app. The generated code looks like this:
// app/serializers/my-model-test.js
// Replace this with your real tests.
test('it serializes records', function (assert) {
var record = this.subject();
var serializedRecord = record.serialize();
assert.ok(serializedRecord);
});
and
// app/adapter/application-test.js
// Replace this with your real tests.
test('it exists', function (assert) {
var adapter = this.subject();
assert.ok(adapter);
});
What do I put in these tests? I've built acceptance tests and unit tests for my models and components, but not sure what needs to go in these unit tests. Haven't been able to find documentation on building these unit tests, nor can I find any example applications on GH that have built these tests out.

If you want to create unit tests for your adapters and serializers, you should look at how ember data tests those themself. Basically, you can look at the test for the RESTSerializer etc. and use their technique.
Example serializer: https://github.com/emberjs/data/tree/master/tests/integration/serializers
The code that ember data uses to achieve this: https://github.com/emberjs/data/blob/master/tests/helpers/store.js

I found it much easier to write an integration test for my custom serializer. I tried Steffans suggestion but I couldn't get it load anything other than the base JSONSerializer. The code I wrote that is working in Ember 1.13.8, Ember Data 1.13.15 is below.
import { moduleFor, test } from 'ember-qunit';
moduleFor('application', 'Integration | Serializer | application', {
integration: true
});
test('Serializer normalizes correctly for basic single object', function(assert) {
assert.expect(1);
let store = this.container.lookup('service:store');
var basicPeterJson = {
id: 1,
title: 'Mr',
firstName: 'Peter',
lastName: 'Parker'
};
var expectedHash = {
data: {
type: 'contact',
id: 1,
attributes: {
title: 'Mr',
firstName: 'Peter',
lastName: 'Parker'
},
relationships: {}
}
};
var contact = store.serializerFor('application').normalizeResponse(store, store.modelFor('contact'), basicPeterJson, 1);
assert.deepEqual(contact, expectedHash);
});
I placed this in tests/integration/serializers/my-serializer-test.js

Testing the adapter:
test('it has a url for creating a record', function (assert) {
const url = this.subject().urlForCreateRecord('person', { firstName: 'Bob' });
assert.equal(url, 'https://example.com/path/to/api');
});
Testing the serializer:
test('it serializes records', function (assert) {
const serialized = this.subject({
foo: 'bar',
}).serialize();
assert.equal(serialized.foo, 'bar');
});
For testing other serializer functions, I previously followed #Knightsy's integration test example and it worked for me. Many thanks! Then, I worked out that this can actually be simplified and unit tested (if you can call it that).
My test goes like this:
moduleForModel('person', 'Unit | Serializer | person', {
needs: ['serializer:person'],
});
test('testing', function (assert) {
const serializer = this.container.lookup('service:store').serializerFor('person');
const payload = {
id: 3,
};
const response = serializer.normalizeSingleResponse(null, null, payload, payload.id);
assert.equal(response.data.id, 3);
});

Related

Ember.js + Mirage: pulling a mocked relationship in integration test

I have a component that makes use of this.get('model.property'), and it works as intended.
For my integration tests I'm using Mirage, which has worked for all my other tests (integration tests included), however when I test this specific component I get:
TypeError: Cannot read property 'then' of undefined
This is what my test looks like:
import { moduleForComponent, test } from 'ember-qunit'
import hbs from 'htmlbars-inline-precompile'
import { startMirage } from 'app/initializers/ember-cli-mirage'
import Ember from 'ember'
moduleForComponent('summary-card', 'Integration | Component | summary card', {
integration: true,
beforeEach() {
this.server = startMirage()
},
afterEach() {
this.server.shutdown()
}
})
test('it renders', function(assert) {
const customer = this.server.create('customer')
const location = this.server.create('location', { customer })
const manufacturer = this.server.create('manufacturer')
const model = this.server.create('device-model', { manufacturer })
this.server.createList('device', 5, { model, customer, location })
const loc = Ember.Object.create(location)
this.set('model', loc)
this.render(hbs`{{summary-card model=model model-name=model.name tag='location' belongs-to='customer' has-many='device'}}`);
assert.equal(this.$('h1').text().trim(), 'Location 0', 'should be titled Location 0')
});
And basically, my summary-card.js has something like this:
this.get('model.' + belongs).then(relationship => {...})
where belongs is simply the value of whatever belongs-to is set to when the component is invoked.
I'm a bit puzzled, as it seems like the mock model I'm passing to my test isn't really representing the model in the same way it does when running ember s (I'm using Mirage for development purposes as well). Is there anywhere where I can find out more about what's exactly going on there?
Thank you!
P.S. I've also tried to use the location object as is provided by server.create(), and I get a slightly different error:
TypeError: _this.get(...).then is not a function
Well, by reading this answer, I managed to find my own solution, which works really well:
import { moduleForComponent, test } from 'ember-qunit'
import hbs from 'htmlbars-inline-precompile'
import Ember from 'ember'
moduleForComponent('summary-card', 'Integration | Component | summary card', {
integration: true
})
test('it renders', function(assert) {
this.inject.service('store', {as: 'store'})
let location
Ember.run(() => {
location = this.store.createRecord('location', {
id: 0,
name: 'Location 0',
customer: this.store.createRecord('customer', {
id: 1,
name: 'Customer 0'
}),
devices: [this.store.createRecord('device', {id: 1})]
})
})
this.set('model', location)
this.render(hbs`
{{#summary-card model=model model-name=model.name tag='location' belongs-to='customer' has-many='device'}}
<div class='test-content'>Test Content</div>
{{/summary-card}}
`)
Basically, I have opted for using the store directly, rather than using Mirage, and it works!

Ember-Data Unit Testing Store

What is the best way to test a model's store? I am using ember-data 2.7.0 and would like to test that I can create a model instance and save it to the backend (firebase) successfully.
I have wrapped the var record = store.create and record.save in and Ember.run function but I get You can only unload a record which is not inFlight. `<(subclass of DS.Model):ember227:null>
Lots of way to test this but the one that I prefer is through spying/stubbing using ember-sinon.
Assuming you have this action for creating and saving a record:
import Route from 'ember-route';
export default Route.extend({
actions: {
createAndSaveTheRecord() {
this.store.createRecord('dummy_model', {
id: 'dummy',
name: 'dummy'
}).save();
}
}
});
You can have a test that looks like this:
import sinon from 'sinon';
test('should create a record', function(assert) {
assert.expect(1);
// Arrange
let stub = sinon.stub().returns({save: sinon.stub()});
let route = this.subject({store: {createRecord: stub}});
// Act
route.send('createAndSaveTheRecord');
// Assert
assert.ok(stub.calledWith('dummy_model', {id: 'dummy', name: 'dummy'}));
});
test('should save the created record', function(assert) {
assert.expect(1);
// Arrange
let spy = sinon.spy();
let route = this.subject({
store: {
createRecord: sinon.stub().returns({
save: spy
})
}
});
// Act
route.send('createAndSaveTheRecord');
// Assert
assert.ok(spy.calledOnce);
});

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()
});
...
});

How create a unit test for view in Ember CLI that renders the view (like tests for components)

I can't find a single example in google for unit test of views in Ember CLI that renders the view (without renders all app).
I wanna this for test events registered inside of didInserElement hook.
For components i can find docs very easy. For render the component in a test with moduleForComponent just do:
test("component test", function(){
var component = this.subject(),
element = this.append();
ok(element.is('.clickable'), 'has the clickable class');
});
But how i do this for views?
I use this way to render only the view in unit tests:
One important thing to note, is that you need to needs all templates, partials and helpers explicitly. Otherwise the test will fail due to lookup errors.
tests/unit/views/main-test.js:
import Ember from 'ember';
import { test, moduleFor } from 'ember-qunit';
var view;
moduleFor('view:main', 'MainView', {
needs: ['template:main'], // won't find the 'main' template without this
setup: function() {
var controller = Ember.ObjectController.extend({
//mockController if needs
}).create();
view = this.subject({
controller: controller,
templateName: 'main',
});
Ember.run(function() {
view.appendTo('#ember-testing');
});
},
teardown: function() {
Ember.run(function() {
view.destroy();
});
},
});
test("didInsertElement", function(){
var element = Ember.$('.main');
var controller = view.get('controller');
var eventForPressCtrlAltM = Ember.$.Event( "keydown", { which: 77, altKey: true, ctrlKey: true } );
Ember.run(function() {
element.trigger(eventForPressCtrlAltM);
});
strictEqual(/* ... */);
});

"Attempting to register an unknown factory" in model test

I have these models in an ember-cli app:
var PuzzleRound = DS.Model.extend({
year: DS.attr('number')
});
var Puzzle = DS.Model.extend({
puzzleRounds: DS.hasMany('puzzleRound', {async: true})
});
And here's my test from tests/unit/models/puzzle-test.js:
import {
moduleForModel,
test
} from 'ember-qunit';
import PuzzleRound from 'weather-roulette/models/puzzle-round';
moduleForModel('puzzle', 'Puzzle', {
// Specify the other units that are required for this test.
needs: ['model:puzzleRound']
});
test('it exists', function() {
var model = this.subject();
// var store = this.store();
ok(!!model);
});
I get this error when running ember test:
Attempting to register an unknown factory: `model:puzzleRound`
I'm using ember-cli 0.1.1, Ember.js 1.7.0, Ember Data 1.0.0-beta.11. Does anyone have anything I can try to fix this?
I just tried out this code with ember-cli 0.0.44 and I got the same error that you did.
I renamed both references to the puzzleRound model path to puzzle-round and then your test passed for me. So:
DS.Model.extend({
puzzleRounds: DS.hasMany('puzzle-round', {async: true})
});
and
moduleForModel('puzzle', 'Puzzle', {
needs: ['model:puzzle-round']
});
I knew that the hyphenated style was preferred over the camelCase style, but I'm not sure when this became mandatory. This requirement may be specific to ember-cli or ember-qunit.
I was looking for a solution similar to this one for awhile, and did not see any mention of my solution so I thought I would post here anyways. It's quite simple really: make sure that the controller you're referencing is actually there.
// my-ember-application/tests/unit/controllers/index/bar-test.js
moduleFor('controller:index/bar', 'My Bar Test', {
beforeEach() { .. }
});
test('it exists', function (assert) {
assert.ok(true);
});
This code would reference a controller at this location:
my-ember-application/app/controllers/index/bar.js