Unit testing components with Mocha

Has anybody had any success unit testing components using Mocha? I have some data transformation going on within the component and want to check that the individual functions are doing what they should.

I have successfully written some tests using this format:

import { expect } from 'chai';
import { describe, it } from 'mocha';
import { setupComponentTest } from 'ember-mocha';

describe('Unit | Component  | my component', function() {
	setupComponentTest('my-component', {
		unit: true
	});

	it('exists', function() {
		let component = this.subject();
		expect(component).to.be.ok;
	});
});

However, I encounter a problem when trying to test a component which has an init(). init() runs as soon as I call this.subject(), and therefore before I can set the props that it needs. My tests therefore error out as init doesn’t have the props it needs to run.

Has anybody else run into similar problems? Component testing seems to be very much focussed on render testing, which seems odd when components can be used to manage chunks of logic, not just visuals.

You can certainly unit test component behavior. I think most advocate treating component tests as black boxes and asserting valid outputs in the DOM given certain inputs. The distinction is still fuzzy to me. Our application mostly has integration tests for components, but also has some unit tests.

In your example, it may be easier to move that logic into methods and then stub them using sinonjs. ember-sinon packages this nicely for ember applications. By making them methods you can stub or spy on them and ensure they have been called as you expect.

import {describe, it} from 'mocha';
import {setupTest} from 'ember-mocha';
import sinon from 'sinon';
describe('some component', function() {
	setupTest();

	let component;
	let fooSpy;

	beforeEach(function() {
		fooSpy = sinon.spy();
		component = this.owner.factoryFor('component:my-component').create({
			foo: fooSpy
		});
	});

	it('has init logic', function() {
		fooSpy.calledOnce.should.be.true();
	});
});

As an aside, the test setup here uses the latest version of ember-mocha that has simplified test setup based on ember testing RFCs.

What are you asserting in your test? Whether properties were passed in?

Edit: I realized after writing this that your question specifically mentioned Mocha and my example code used QUnit. Sorry 'bout that.

Small point here. If you are instantiating a component to unit test a function/property on that component then I would argue that function/role is not a component concern and should instead be moved out of the component and into a utility file. The distinction is that unit tests help to verify the contract/API that an internal object exposes. Much like you would not test private functions of a class because that is implementation details you just want to know that if your class has a prescribed state that it behaves in an expected way.

Components, however, play a specific role in that they manage a section of the DOM. Looking at components from that role would mean your public interface/API is the DOM itself and therefor should be tested as integration tests leaving the nitty gritty as implementation details and test the interface using the messaging framework that is the integration tests.

I do this even for utility like components which don’t actually involve the DOM other then to provide data to other components I will also design the test as an integration test.

Ultimately my rule of thumb is if my public interface my subject under test is meant to be used by more JavaScript code then I will unit test it. If my public interface under test is meant to be used in a template then I will integration test it.

Another great rule of thumb I use is that if I unit test an object and cover its edge cases then I am free to mock its use elsewhere (most likely in an integration test). For example if I unit test my model then I now have permission to make fakes, mocks, even disparate duck types of that model for components that would normally take that model as an attribute. With this rule of thumb I also expand that to: if I integration my components so that they cover all the edge cases that component is responsible for then I have permission to make an acceptance test that is just the happy path knowing all the edge cases have been handled in integration tests.

For clarification, in my integration tests I will use the following pattern/practices:

Stub actions with spies

Either manual or Sinon.JS (if given the choice).

let callCount = 0;
this.set('actionSpy', () => callCount++);
await render(hbs`<MyComponent @myAction={{action this.actionSpy}} />`);
assert.equal(callCount, 1);

Mock data passed in

this.set('testData', 'foo');
await render(hbs`<MyComponent @data={{this.testData}} />`);
let result = find('#my-component [data-test=rendered-data]').textContent.trim();
assert.equal(result, 'foo');
run(() => this.set('testData', 'bar');
result = find('#my-component [data-test=rendered-data]').textContent.trim();
assert.equal(result, 'bar');

In the case of a model I might use a POJO or if the component needs the ember-data model API:

const MockModel = EmberObject.extend({
  save() {}
});

// ...

  this.set('testData', MockModel.create({
    name: 'foobar'
  }));

Provide outlets for derived state that are yielded

await render(hbs`
  <MyComponent as |value|>
    <span id="test-value">{{value}}</span>
  </MyComponent>
`);
let result = find('#test-value').textContent.trim();

I also use buttons to click for any actions a component yields.

Use Page Objects for any inspection of the DOM inside the template

ember-cli-page-object is amazing for this!

1 Like