Using ember-qunit to test controllers with a store (DS.FixtureAdapter) - javascript

I have an ember-qunit test case for a controller (using moduleFor('controller:name', ...)) that that I'd like to be able to use the moduleForModel-exclusive this.store() in order to retrieve a DS.FixtureAdapter data store. For this specific test case, I'm not trying to test the model - I just want to verify that the controller can be populated with a set of model instances and various operations can be run against that data.
I'm using coffeescript so my code looks like:
moduleFor("controller:test", 'My Controller', {
setup: ->
#store().createRecord 'test', value: 1
#store().createRecord 'test', value: 2
#subject({
model: #store().all('test')
})
teardown: -> App.reset()
}, (container, context) ->
container.register 'store:main', DS.Store
container.register 'adapter:application', DS.FixtureAdapter
context.__setup_properties__.store = -> container.lookup('store:main')
)
In the example above there is a controller named TestController and there is also a model named Test. I lifted the container.register and context.__setup_properties__.store lines from the definition of moduleForModel in ember-qunit.
The problem is that I get an error when running the ember-qunit test suite:
Setup failed on [test case name]: No model was found for 'test'
Running the actual application outside of ember-qunit works fine. Maybe there's somebody out there who's had this same issue? Or maybe I'm taking the wrong approach?

Your problem could be that your test model has not been registered in the container, so it cannot be resolved.
You could register manually during your test module callbacks:
container.register('model:test', TestModel)
Or use the needs property of the moduleFor impl:
moduleForComponent('controller:test', 'My Controller', {
// specify the other units that are required for this test
needs: ['model:test'],
setup: {...},
teardown: {...}
});

Related

Ember's *needs* dependencies

I have a signup process that consists of a few steps and would like to store the state within a service that can be accessed by each of the controllers for each of the steps.
I was able to get this working, but in a way that doesn't seem to jive with Ember's way of doing things. Instead of setting the controller's needs: value I had to add an initializer, which contains the following:
export default {
name: 'signup-state',
initialize: function(container, app) {
app.inject('controller:signup/index', 'signup-state', 'service:signup-state');
app.inject('controller:signup/method', 'signup-state', 'service:signup-state');
app.inject('route:signup/method', 'signup-state', 'service:signup-state');
}
};
The above was based on a comment by wycats on the discuss board [1].
Doing this just seems wrong. I would think that the needs controller would take care of this. So if this is just plain wrong stop me here since doing this a better way may fix the problem.
The above works, except for when it comes time to test the controller. When I call a method on the controller, that calls a method on the service, I get an error.
Here is the controller code
export default Ember.Controller.extend({
/**
* Reference to the signup-state service => initializers/signup-state.js
*/
setState: function(key, val) {
var state = this.get('signup-state');
state.set(key, val); <== state is undefined in tests
},
actions: {
signupAsAdmin: function() {
this.setState('userType', 'admin');
this.transitionToRoute('signup.method');
}
}
});
And here is the controller TEST code
import { test, moduleFor } from 'ember-qunit';
moduleFor('controller:signup/index', 'SignupController', {
needs: ['service:signup-state']
});
test('signing up as an admin set the userType state to admin', function() {
var controller = this.subject();
// blows up here => Cannot read property 'set' of undefined
controller.send('signupAsAdmin');
});
Calling the signupAsAdmin function within the controller, results in making a set call on the service object, which results in an “undefined” error.
The initializer code is run as noted by adding console.log statements, but doesn't seem to result in making the service available to the controller during the tests.
Any help is appreciated.
Note: I am using ember-cli, so I don't have a global App variable available.
Update Manually registering (something I thought that ember-cli was doing) does work.
export default {
name: 'signup-state',
initialize: function(container, app) {
app.register('service:signup-state', 'signup-state');
// Remove Injects
// app.inject('controller:signup/index', 'signup-state', 'service:signup-state');
// app.inject('controller:signup/method', 'signup-state', 'service:signup-state');
}
};
The above results in a null value returned when calling the get('signup-state') in the controller.
http://discuss.emberjs.com/t/services-a-rumination-on-introducing-a-new-role-into-the-ember-programming-model/4947/10?u=olsen_chris
I'm new to the idea of using the dependency injection for a service so I might be missing something, but looking at this example test in the ember code base made me wonder, are you just missing a app.register('service:signup-state',App.ModelForSignupState) to give it bones?

Ember.js: dependencies between two controllers failing

I am trying to access one of two models in a controller that uses needs on a sibling controller. My router looks like the following:
App.Router.map(function() {
this.route('login');
this.route('mlb.lineups', {path: 'tools/mlb/lineups'})
this.resource('mlb.lineups.site', { path: 'tools/mlb/lineups/site/:site_id' });
});
The mlb.lineups route definition looks like the following:
App.MlbLineupsRoute = Ember.Route.extend({
model: function() {
var self = this;
return Ember.RSVP.hash({
sites: self.store.find('site')
})
},
setupController: function(controller, models) {
controller.set('model', models.get('sites'));
},
afterModel: function(models) {
var site = models.sites.get('firstObject');
this.transitionTo('mlb.lineups.site', site);
}
});
The reason I am using Ember.RSVP.hash({}) here is I plan on adding another model to be retrieved after I retrieve the site model.
Now in my MlbLineupsSiteController I am trying to access the sites model with the following:
App.MlbLineupsSiteController = Ember.ArrayController.extend({
needs: "mlb.lineups",
sites: Ember.computed.alias("controllers.models.sites")
});
This is the error I'm getting in my Ember console: needs must not specify dependencies with periods in their names (mlb.lineups)
What's the best way to make the sites model from the MlbLineups controller available in my MlbLineupsSiteController?
Note:
#NicholasJohn16's answer isn't valid anymore. It always gives an error that controller couldn't be found. Generally you should also never use needs property and always use Ember.inject.controller if you have to make your controllers dependent on each other. I'd also recommend using services instead of dependencies between controllers. It's easier to maintain code which contains communication between controllers through services, than controller directly accessing other controller's properties. You might not always be aware of such access, and using services gives you another layer of security.
Solution:
Tested in Ember.js 1.10.0-beta.4. Use following code in Controller to reference nested controller in needs:
needs: ['classic/about']
Then you can access it later using:
const aboutController = this.get('controllers.classic/about');
const aboutProperty = aboutController.get('customProperty');
Works as expected. Basically you need to replace dots with slashes.
It should be:
needs:" MlbLineupsSite "
Basically, the name of the controller you want to include, minus the word controller.
Everything else you posted should work.

Ember.js dependency injection

let's assume I have this controller
MyApp.LayoutFooterController = Ember.ObjectController.extend
formData:
name: null,
phone: null,
message: null
cleanFormData: ->
#set('formData.name', null)
#set('formData.phone', null)
#set('formData.message', null)
send: () ->
#container.lookup('api:contact').send(
#get('formData.name'),
#get('formData.phone'),
#get('formData.message')
)
#cleanFormData()
For this I've created service class
MyApp.Api ||= {}
MyApp.Api.Contact = Ember.Object.extend
init(#$, #anotherDep) ->
send: (name, phone, message) ->
console.log name, phone, message
and initializer
Ember.Application.initializer
name: 'contact'
initialize: (container, application) ->
container.register 'api:contact', MyApp.Api.Contact
Problem is, that I can not figure out how to set container to be able to resolve my service class dependecies init(#$, #anotherDep) through Ember container.
Can anybody give me explanation, how to use the Ember.js dependecy injection (or service locator, I guess) container to inject other libs or objects?
Maybe, that I'm not doing it well at all.
EDIT
When I looked to Ember's container source code I found a solution:
Ember.Application.initializer
name: 'contact'
initialize: (container, application) ->
container.register 'api:contact', { create: () -> new MyApp.Api.Contact(application.$) }
But is this clean?
Generally you don't want to be wiring up all of the pieces yourself, you want to use needs in your controller to let Ember do it for you. I'm not sure at all how Ember deals with 3 level class names vs two, so I'm just going to demonstrate with two levels. (MyApp.ApiContact instead of MyApp.Api.Contact.) Also, send is a native Ember method that is present on all (or almost all) objects, so you'd want to use something like sendMessage instead so that you don't end up with hard to diagnose conflicts. After you have told Ember that your controller needs apiContact, you can just call this.get('controllers.apiContact') to get a hold of it.
MyApp.LayoutFooterController = Ember.ObjectController.extend({
needs : ['apiContact'],
// All your other stuff here
sendMessage : function(){
this.get('controllers.apiContact').sendMessage(...);
}
});

Sencha Touch 2 Global Variables - Access everywhere

I know this question was already posted in StackOverflow but I either didnt understand or sencha changed somewhat.
My app loads a form panel for login, then I would like to save the user info that have just loged on. This way I can change my view anytime I want and still know the name of the loged in user.
Here is my main code:
//<debug>
Ext.Loader.setPath({
'Ext': 'sdk/src'
});
//</debug>
Ext.application({
name: 'APP',
loadedUser: 'the test',
requires: ['Ext.MessageBox'],
views: ['Main', 'Home', 'Login'],
models: ['User'],
stores: ['Users'],
controllers: ['Main'],
icon: {
57: 'resources/icons/Icon.png',
72: 'resources/icons/Icon~ipad.png',
114: 'resources/icons/Icon#2x.png',
144: 'resources/icons/Icon~ipad#2x.png'
},
phoneStartupScreen: 'resources/loading/Homescreen.jpg',
tabletStartupScreen: 'resources/loading/Homescreen~ipad.jpg',
setLoadedUser: function(arg) {
this.loadedUser = arg;
},
launch: function() {
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
// Initialize the main view
Ext.Viewport.add(Ext.create('APP.view.Main'));
},
onUpdated: function() {
Ext.Msg.confirm("Application Update", "This application has just successfully been updated to the latest version. Reload now?", function() {
window.location.reload();
});
}
});
The 'loadedUser' its what I wanted to be my global variable, and the method setLoadedUser(arg) its suposed to change that value.
I can access 'loadedUser' no problem, but I can't change its value.
Another question: loadedUser can it be an array/data structure?
How are you accessing the function? This works for me. Remember you should access it like this:
APP.app.setLoadedUser('test');
And yes, it can be any value. :)
You can also use localStorage to, set/get Your variables:
Set it as:
localStorage.setItem('currentUserId', userID)
Get it as:
localStorage.getItem('currentUserId')
You can use it, anywhere in Your script.
Yes, the works when the function is inside of the app.js file.
It does not work if the function is inside of the controller file.
So if you have a project application called IronMan, the call from the view code to the global function, flyAway(), in your app.js file would look like:
IronMan.app.flyAway();

Mock/Stub constructor

I have the following code:
class Clients
constructor : ->
#clients = []
createClient : (name)->
client = new Client name
#clients.push client
I am testing it with Jasmine BDD like this:
describe 'Test Constructor', ->
it 'should create a client with the name foo', ->
clients = new clients
clients.createClient 'Foo'
Client.should_have_been_called_with 'Foo'
it 'should add Foo to clients', ->
clients = new clients
clients.createClient 'Foo'
expect(clients.clients[0]).toEqual SomeStub
In my first test I want to check if the constructor is being called with the correct name. In my second I just want to confirm that whatever came out of new Client was added to the array.
I am using Jasmine BDD and it has a way to create spies/mocks/stubs but it seems it's not possible to test constructor. So I am looking into a way to test the constructor it would be nice if there is a way that I don't need an extra library but I am open to anything.
It is possible to stub out constructors in Jasmine, the syntax is just a bit unexpected:
spy = spyOn(window, 'Clients');
In other words, you don't stub out the new method, you stub out the class name itself in the context where it lives, in this case window. You can then chain on a andReturn() to return a fake object of your choosing, or a andCallThrough() to call the real constructor.
See also: Spying on a constructor using Jasmine
I think the best plan here is to pull out the creation of the new Client object to a separate method. This will allow you to test the Clients class in isolation and use mock Client objects.
I've whipped up some example code, but I haven't tested it with Jasmine. Hopefully you can get the gist of how it works:
class Clients
constructor: (#clientFactory) ->
#clients = []
createClient : (name)->
#clients.push #clientFactory.create name
clientFactory = (name) -> new Client name
describe 'Test Constructor', ->
it 'should create a client with the name foo', ->
mockClientFactory = (name) ->
clients = new Clients mockClientFactory
clients.createClient 'Foo'
mockClientFactory.should_have_been_called_with 'Foo'
it 'should add Foo to clients', ->
someStub = {}
mockClientFactory = (name) -> someStub
clients = new Clients mockClientFactory
clients.createClient 'Foo'
expect(clients.clients[0]).toEqual someStub
The basic plan is to now use a separate function (clientFactory) to create new Client objects. This factory is then mocked in the tests allowing you to control exactly what is returned, and inspect that it has been called correctly.
My solution ended up similar to #zpatokal
I ended up using a module accross my app (not really big app), and mocking from there. One catch is that and.callThrough won't work, as the constructor is going to be called from the Jasmine method, so I had to do some trickery with and.callFake.
On unit.coffee
class PS.Unit
On units.coffee
class PS.Units
constructor: ->
new PS.Unit
And on the spec files:
Unit = PS.Unit
describe 'Units', ->
it 'should create a Unit', ->
spyOn(PS, 'Unit').and.callFake -> new Unit arguments... # and.callThrough()
expect(PS.Unit).toHaveBeenCalled()
Clearer solution with recent jasmine version:
window.Client = jasmine.createSpy 'Client'
clients.createClient 'Foo'
expect(window.Client).toHaveBeenCalledWith 'Foo'

Categories