-
Notifications
You must be signed in to change notification settings - Fork 105
Testing: Creating Component Tests
Components remove responsibility from the controller/page by managing their own dependencies, state, and view. In the same way, each component should be wrapped in a test harness (in the test/e2e/components directory) that removes responsibility from the test script. This lets a developer write logical tests, rather than lots of boilerplate code to interact with each component.
As an example, we will be building a fictional bhPatientCard
component to display a patient's medical information in a consistent manner. The HTML signature will look like this:
<bh-patient-card patient-id="SomeController.patientId"></bh-patient-card>
The first step to adding testing to a component is to make a namespace tag for it. Inside the template for the bhPatientCard
component, we will put a top-level data-* attribute. Using a data-*
attributes is recommended since they allow detailed descriptions, allow assignment (e.g data-example-option="1"
, and can be repeated multiple times in the HTML page.
<!-- mark the top level element-->
<div data-bh-patient-card>
<!-- contents of the patient card -->
</div>
You should never use an id
attribute as a namespace - placing more than one component on the page will violate the HTML spec. See this reference for more details.
Once the component has been namespaced, it can easily be found on a page. If more than one instance of the component exists on a page, it can be uniquely identified by the parent controller via an id
attribute. For example, suppose we have the following situation:
<div class="patient-list">
<bh-patient-card ng-repeat="patient in ParentController" id="card-{{ patient.id }}" patient-id="patient.id">
</bh-patient-card>
</div>
Since we used a data-*
attribute to namespace the client, this does not violate the HTML spec's id
attribute rules.
The test harness should include the namespace as a selector.
// inside test/e2e/shared/components/bhPatientCard.js
module.exports = {
selector : '[data-bh-patient-card]',
/* harness methods ... */
};
Let's assume that the bhPatientCard
has a button to click on it, which will close the card. We'll make a wrapper method to perform this action, called close()
. Let's look at the HTML template:
<div data-bh-patient-card>
<!--
... patient information displayed in a logical fashion ...
-->
<button class="btn btn-default" data-action="close">
Close
</button>
</div>
So, we need a method to locate the button, and click it.
module.exports = {
selector : '[data-bh-patient-card]',
// this method closes the patient card
close : function close() {
// locate the patient card on the page
var card = element(by.css(this.selector));
// locate the <button> inside the patient card
var btn = card.element(by.css('[data-action="close"]'));
// click the button!
btn.click();
}
};
Now, in our test, we can use the wrapper like this:
var bhPatientCard = require('path/to/bhPatientCard.js');
describe('Some Controller', function () {
it('Can close the patient card', function () {
bhPatientCard.close();
});
});
The close()
method works well for a single component, but what happens if we have more than one <bh-patient-card>
on the page? The close()
method could take in an id
, and use that to locate and close the correct card. Let's modify our method:
module.exports = {
selector : '[data-bh-patient-card]',
// this method closes the patient card. If an id is provided, it will locate the card with that id
close : function close(id) {
// locate the patient card on the page, using an id if it exits.
var card = element(id ? by.id(id) : by.css(this.selector));
// locate the <button> inside the patient card
var btn = card.element(by.css('[data-action="close"]'));
// click the button!
btn.click();
}
};
Now we can write the following test:
var bhPatientCard = require('path/to/bhPatientCard.js');
describe('Some Controller', function () {
it('Can close the correct patient card', function () {
// assert the card exists
expect(element(by.id('card-3')).isPresent()).to.eventually.be.true;
// close card with id = 'card-3'
bhPatientCard.close('card-3');
// assert the card has been removed
expect(element(by.id('card-3')).isPresent()).to.eventually.be.false;
});
it('Can still close a patient card without an id', function () {
// in this case, we expect only one patient card to be on the page.
expect(element(by.css(bhPatientCard.selector)).isPresent()).to.eventually.be.true;
// close the only patient card on the page
bhPatientCard.close();
// make sure there are no more patient cards on the page.
expect(element(by.css(bhPatientCard.selector)).isPresent()).to.eventually.be.false;
});
});