I was brought in on the "back-end" of a project and asked to help write tests for an app. I am very new to Ember and need just a little help getting started. We are trying to provide unit test for the routes, so we can have a bit more molecular scope over the app, instead of acceptance test. I have looked at some tutorials and went through every possible scenario that I could think of. I just need a bit of jumpstart.
Here is the part of the route.js for this route.
down stream of this parent route, we have another nested route that shows a list of contacts and when a user clicks on a show button it calls "model" and returns that variable "rec" for the template and the url
export default Route.extend(ScrollTo, {
flashMessages: service(),
model: function(params) {
let rec= this.store.peekRecord('contact', params.contact_id);
return rec;
},
actions: {
saveContact: function() {
let model = this.currentRouteModel();
model
.save()
.then(() => {
//this.refresh();
//this.setModelHash();
this.flashMessages
.success(`Saved Contact: ${model.get('title')}`);
//this.transitionTo('contacts');
});
}
Here is pre-generated code for the test. I really haven't made any modifications, because I really didn't know where to start.
This app doesn't have a back-end, it's design is to take in information and provide a iso file based on whatever standard the user wants.
I am sure I need to provide some mock data for the test and send that to the method, but again I am not sure what Qunit parts I use.
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | contact/show', function(hooks) {
setupTest(hooks)
test('it exists', function(assert) {
var route = this.owner.lookup('route:contact/show');
assert.ok(route, 'contact.show route works');
});
});
I struggled with this as well when I first moved to frontend testing. With a few years experience I'm confident in saying you shouldn't unit test the route objects in this way. Instead the testing of an ember app should focus on two types of tests.
What ember calls integration tests (but are actually closer to UI unit tests)
Acceptance tests
Integration tests on components allow you to get into the smaller details and edge cases and they can be super valuable.
What I think you want in this case is actually an acceptance test.
If your testing experience is at all like mine it will feel like you're testing too many things at first, but these types of tests where the browser actually loads the entire application have the most value in hunting down bugs and are the most maintainable over time.
Related
I have a component "nested" which makes a web request, and I'm using this component from another component "parent".
I'm trying to write some integration tests for "parent", but they are failing as the "nested" component's web requests are failing.
Instead of mocking out the requests, I was just hoping to mock some of the "nested" functionality to prevent the web request. This is easily achievable with reopen, but of course this will cause the tests for "nested" to fail.
Does anyone know if there is a way that I can either stub pieces of "nested", or maybe use the registry to replace "nested" with an extended class?
To swap out a component, just for the purpose of a single test module, simply register a custom component to replace the original (only for the scope of the test module):
moduleForComponent('component-under-test', 'description', {
integration: true,
beforeEach() {
this.container.registry.register('component:nested-component', NestedComponent.extend({
modifiedFunction() {
}
}));
}
});
import Ember from 'ember';
import startApp from '../../helpers/start-app';
var App;
module('Integration | Authentication | Abilities', {
integration: true,
setup() {
App = startApp();
},
teardown() {
Ember.run(App, 'destroy');
}
});
test('Only SuperUsers, Researchers and AccountHolders can see messages', function(assert) {
visit('/');
assert.equal(find('div').text(), 'fsfsfd');
});
This is an integration test I'm trying to get working so I can test basic user interaction in our ember-cli app. The problem is that this simple test does only returns empty strings whenever I search the DOM. It is not hitting an unauthorized page or anything it's just returning nothing from any testHelpers. currentURL, currentPath return undefined.
Am I missing something absolutely fundamental in my understanding of how integration tests work?
I'm trying to test how ember-can gives and denies permissions to users based on their title. However, I may as well just be testing whether or not the logo shows up in the right corner because I can't see anything on the page at the moment.
I think what you're missing is that tests are asynchronous. Transitions involve promises (typically loading models) so you need to wait for the visit to complete. You can use the andThen helper:
test('Only SuperUsers, Researchers and AccountHolders can see messages', function(assert) {
visit('/');
andThen(function() {
assert.equal(find('div').text(), 'fsfsfd');
});
});
Here's more info in the guides
I'm posting because it turns out the problem was the way our website had been set up with a rootURL. I had to put this line inside our startApp.js file: setResolver(Ember.DefaultResolver.create({ namespace: "/cli/ea/" }));
It looks like the resolver was taking me to localhost:4201/ which is not actually going to be used because we are proxying from rails (which is localhost:3000). Therefore, nothing was coming back from any DOM interaction because there was no route and no template set. currentURL and other helpers returning undefined I guess was the only piece that was unusual in hindsight.
TLDR
I'm trying to unit test a very simple component. However, it appears some very common test helpers aren't being defined. This is something specific about unit-testing a component, as I'm using these in integration tests without issue.
Now just jump straight to the Questions at the end.
Details
The errors are generic:
click is not defined
andThen is not defined
Stack trace for context:
Died on test #4 at Object.test (http://localhost:7357/assets/test-support.js:110:11)
at http://localhost:7357/assets/skylab.js:14977:15
at mod.state (http://localhost:7357/assets/vendor.js:150:29)
at tryFinally (http://localhost:7357/assets/vendor.js:30:14)
at requireModule (http://localhost:7357/assets/vendor.js:148:5)
at Object.TestLoader.require (http://localhost:7357/assets/test-loader.js:29:9)
at Object.TestLoader.loadModules (http://localhost:7357/assets/test-loader.js:21:18): click is not defined
The component and the tests are very basic. The component:
import Ember from 'ember';
export default Ember.TextField.extend({
classNames: ['input-span']
});
The Test:
import Ember from 'ember';
import {
moduleForComponent,
test
} from 'ember-qunit';
moduleForComponent('custom-input');
test('focus on click', function(assert) {
assert.expect(1);
var component = this.subject();
this.render();
click('input')
assert.ok(component.$('input').is(':focus'));
});
Best Guess
My best guess is that these helpers work in the acceptance tests because the startApp helper creates the click and andThen helper functions. I don't have setup & teardown code in my moduleForComponent call, but it doesn't look like I should need it. And I don't want to test the whole app here -- just an isolated component.
Questions
Is there another way to inject these test helpers that I'm unaware of?
Am I writing these tests wrong? Should I never use click in a component test? Is the documentation simply outdated?
Should this be supported as-written, and is this a framework bug I should report?
acceptance level helpers currently depend on an app being spun up, as such they are not available for unit level tests. As those do not have an app.
I'm writing a simple qunit test that has a controller setup w/ a few items in an array
App.UploadController = Ember.Controller.extend({
to_upload: Ember.A([])
});
I add stuff to this "to_upload" array and verify it between tests. One issue I've found that that if I don't "manually clear it" like this in my test "tear down" it holds onto this state (even with an App.reset() being called in each teardown (calling destroy).
App.__container__.lookup("controller:upload").clear_files()
(the clear files is a simple monkey patch I added in my test helper -not production code)
App.UploadController.reopen
clear_files: function() {
this.get('to_upload').clear();
}
Here is my tear down for the qunit tests
module "/upload",
teardown: function() {
App.reset()
App.__container__.lookup("controller:upload").clear_files()
}
It's probably related to the fact that assigning an array to a property in the class definition makes it a class property and not an instance property. I'm not sure if this is documented somewhere, I've read it before a few times though. I'll update with documentation if I can find it.
Until then you can probably prove my theory by modifying the code to look like this.
App.UploadController = Ember.Controller.extend({
init: function(){
this.set('to_upload', Em.A([]));
this._super();
},
to_upload: null
});
You can read about it in step 6 here http://codebrief.com/2012/03/eight-ember-dot-js-gotchas-with-workarounds/
I'm pretty sure this should be put somewhere in the docs, I'm not sure where the most obvious would be though.
I've read a lot of Zend controller testing tutorials but I can't find one that explains how to test a controller that uses models and mocking those models.
I have the following controller action:-
function indexAction(){
// Get the cache used by the application
$cache = $this->getCache();
// Get the index service client and model
$indexServiceClient = new IndexServiceClient($this->getConfig());
$indexModel = $this->_helper->ModelLoader->load('admin_indexmodel', $cache);
$indexModel->setIndexServiceClient($indexServiceClient);
// Load all the indexes
$indexes = $indexModel->loadIndexes();
$this->view->assign('indexes', $indexes);
}
At the moment I have a very basic test case:-
public function testIndexActionRoute() {
$this->dispatch( '/admin/index' );
$this->assertModule('admin', 'Incorrect module used');
$this->assertController('index', 'Incorrect controller used');
$this->assertAction('index', 'Incorrect action used');
}
This test works but it's calling the real models and services, which sometimes means it times out and fails on the test environment. In order to properly unit test just the controller I need to have mocks and expectations for IndexServiceClient and IndexModel - how is this done?
Well, since not many replies I am seeing here, I'll try to add my 2cents(potentially arguable).
Answer written below is my IHMO and very subjective(and I think not very useful, but here we go anyway)
I think controllers are not a good fit unit testing. Your business logic layer, models etc. is what is unitestable.
Controllers are connected with UI and bring system together so to speak - hence to me they are a better fit for integration and UI testing - something that packages such as Selenium are used for.
To my mind testing should be easy enough to implement such that total effort for testing implementation is adequate to the returns of it. Wiring up all dependencies for a controller seems to me(with my limited knowledge of course) a bit too much of a task.
The other way to think about it is - what is actually going on in your controllers. Again IHMO it supposed to be primarily a glue level between your business logic and your UI. If you're putting a lot of business logic into controller it will have an adverse effect(for instance it won't be easily unitestable..).
This is all sort of theory of course. Hopefully someone can provide a better answer and actually show how to easily wire up a controller for unit tests!
One possible solution that a colleague put forward is to use a Zend Controller Action Helper to inject mock dependencies. This should work in theory but I've yet to extensively test this method
Here is an example of doing it this way.
class Mock_IndexModel_Helper extends Zend_Controller_Action_Helper_Abstract {
private $model;
public function __construct($model) {
$this->model = $model;
}
/**
* Init hook
*/
public function init() {
$this->getActionController()->setIndexModel( $this->model );
}
}
class IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase {
public $bootstrap = BOOTSTRAP;
public function setUp(){
parent::setUp();
}
/**
* Test the correct module/controller/action are used
*/
public function testIndexAction() {
$mockIndexModel = $this->getMock("IndexModel");
Zend_Controller_Action_HelperBroker::addHelper(new Mock_IndexModel_Helper($mockIndexModel));
$this->dispatch( '/admin/index' );
$this->assertModule('admin', 'Incorrect module used');
$this->assertController('index', 'Incorrect controller used');
$this->assertAction('index', 'Incorrect action used');
}
}