This is the second in a multi-part series looking at custom Ember Blueprints. In part one we built an Ember Blueprint to generate read, edit and create form pages for an Ember Model, based around its attributes. At the end, we’d got a working blueprint. However, we didn’t test it, and it was only really useful within the project we authored it.
This second part is going to start and address those issues.
Now, I’ve got some good news and some bad news: the bad news is, if you’re not familiar with Ember, Ember Add-Ons, and Ember Blueprints then there is a lot of information in here. In addition, we’re going to be heading into some choppy, un-documented waters.
But don’t worry, I’ve made this post as accessible as possible and dropped in a number of support links throughout. Go as slow or fast as you like. Read it twice. Scan it to get a feel and then park it for reference later. It’s your content now, and I won’t be offended if you decide to leave. Much.
Follow Along At Home
The completed add-on for this blog post can be found on Github here.
At the end of each section where I’ve made a commit, I’ll include a link.
Creating Our Add-On
Ember Add-Ons are created just like Ember Applications, by using the Ember CLI. In this case the command ember addon <add-on-name> will create a folder <add-on-name> and add all of the required files to get started. To create the add-on in the current directory then simply ember addon <add-on-name> --directory ..
An outline of the Add-On project structure can be found here.
However, the blueprint folder isn’t automatically created for you, as it’s only required if your add-on is going to provide Blueprints. This one definitely is, so we’re going to need that folder.
The content for our Blueprint was already created in the last blog post, and exists in the my-first-blueprint branch. Fortunately, Git never ceases to amaze me, and there is a command for copying a folder from one branch to another. Well, of course there is…
> git checkout origin/my-first-blueprint -- blueprints
> git commit -m “Moved over the blueprints folder from the my-first-blueprint branch”
It also requires a simple npm install --save string which is used by the Blueprint’s index.js when processing the generate request.
And with that, our Add-On should be ready to be imported into another Ember project. It’s easily tested.
> cd ~
> ember new addon-test
> cd addon-test
> ember install "git+ssh://git@github.com:mediasuitenz/mediasuite-ember-blueprints.git#2b95cdf"
NPM: Installed git+ssh://git@github.com:mediasuitenz/mediasuite-ember-blueprints.git#2b95cdf
WARNING: Could not figure out blueprint name from: "git+ssh://git@github.com:mediasuitenz/mediasuite-ember-blueprints.git#2b95cdf". Please install the addon blueprint via "ember generate <addon-name>" if necessary.
Installed addon package.
> ember g model-crud person
ENOENT: no such file or directory, open '/Users/jonathanh/ember-addon-test/app/models/person.form.json'
Oddly enough, despite throwing an error, this is good news. This simply indicates we don’t have a model config for the person model, which is very true.
The other warning that we received isn’t relevant to this project, but if you’re curious it’s related to this issue.
Testing
So now we’ve proven the Blueprint is installable to other projects, we need to ensure it’s working correctly. For that we need some tests.
Testing Blueprints is not a well documented process, so let’s break down what we’d like to do:
- Create a test instance of an Ember application.
- Generate our model-crud Blueprint from a known Ember Model config into the test instance of the Ember application.
- Run functionality tests against the test application to check the output of the Blueprint works as we hope.
- Tidy up, so we can repeat this test at will as we make updates to the Blueprint.
The Ember eco-system is chock-full of great add-ons, and in fact there is one to help with testing Blueprints.
Ember-cli-blueprint-test-helpers
Once you have ember install ember-cli-blueprint-test-helpers within your project, you can use the ember generate blueprint-test my-blueprint command to generate the outline of a blueprint test.
The outline test exists in node-tests/my-blueprint-test.js , which roughly does the following when executed:
- Creates a temporary folder within the project and initializes a bare-bones Ember project inside it. No NPM or Bower modules are installed.
- The Blueprint under test is generated within this temp Ember project.
- There is now an opportunity in the code to run some synchronous tests to ensure the files are in the correct location and they contain appropriate text.
- The Blueprint under test is then destroyed.
- Another opportunity to check that the files were removed correctly
That looks awesome, so let’s start there.
Ember-cli-blueprint-test-helpers
First off we need to install these into our project and generate our test harness.
> ember install ember-cli-blueprint-test-helpers
> ember generate blueprint-test model-crud
This makes a number of minor changes, including to the package.json to add a test execution command, and drops a test harness file into the project:
If we follow that up by trying to now test:
> npm run nodetest
mediasuite-ember-blueprints@0.0.0 nodetest /Users/jonathanh/mediasuite/mediasuite-ember-blueprints
mocha node-tests --recursive
Acceptance: ember generate and destroy model-crud
ENOENT: no such file or directory, open '/Users/jonathanh/mediasuite/mediasuite-ember-blueprints/tmp/tmp-78688-ofqjw0oxkggc4so/my-app/app/models/foo.form.json'
Then we get a bunch of errors! But again, this is actually good news. Notice the test is running, and the Blueprint is failing to generate code because it can’t find the foo.form.json config file within the test Ember application.
That’s okay! We still haven’t got around to setting up any of test fixture files at all yet. We’ll do that shortly, but first, we’ve got a big problem here that can’t be put off.
Houston, We Have a Problem
Ember-cli-blueprint-test-helpers is our friend, but it doesn’t work quite the way we’d like out of the box. By default, it spins up an Ember app, generates from the Blueprint, and allows rudimentary ‘did you generate in the right place with the right content’-type tests. We’re looking to go one, or maybe several, steps further. Not only do we want to generate from the Blueprint, but then launch the Ember app and execute Ember tests against the running instance.
This is where things start to get hairy. I should also take this moment to thank Tobias Bieniek (@simplabs) – one of the main authors of the add-on – for taking some time to discuss this with me. The Ember community is a great place, and no small reason why we love the framework.
The good news is: what we want to do is possible.
The bad news: to work it out, I had to spend a lot of time spelunking through the add-on’s source code.
The good news again: you don’t have to.
Let’s look at the key piece of the original model-test-crud.js code:
it('model-crud foo', function() {
var args = ['model-crud', 'foo'];
// pass any additional command line options in the arguments array
return emberNew()
.then(() => emberGenerateDestroy(args, (file) => {
// expect(file('app/type/foo.js')).to.contain('foo');
}));
});
It’s a beautiful, succinct thing, but we really need something more like:
it('model-crud foo', function() {
var args = ['model-crud', 'foo'];
return emberNew()
.then(() => {
// 1. set up any initial test fixture data (e.g. the models and model config)
// 2. add any required module references to package.json
// 3. npm install so we can launch Ember
}
.then(() => {
// 4. Generate the Blueprint instance to test
}
.then(() => {
// 5. Run the ember tests
})
// Because our promise is an RSVP promise, we can use...
.finally(() => {
// 6. Tear down the all the test files to leave the slate clean for the next run
})
})
Let’s take each of those steps in turn.
Set-up Test Fixture Data
Because at heart I’m a traditionalist, let’s go with Books and Authors. Therefore, we’ll create a Book Model with a:
- title (string)
- Synopsis (string)
- rating (number)
- author (belongsTo Author)
The Author simply has:
- name (string)
- dateOfBirth (date)
And a config file for the Book which we’ll use to build the form.
There is a tests/dummy folder in our project structure already which contains the structure of an Ember application. Normally it’s used for testing Add-ons, but we can use it for storing our fixture data.
As our Blueprint doesn’t yet update the router.js file, we’ll add that into the tests/dummy/app folder as well with the known content:
this.route('new-book', {path: 'book/new'})
this.route('book', {path: 'book/:book_id'}, function () {
this.route('view', {path: '/'})
this.route('edit')
})
Copy Our Fixture Data
Now we have our fixture data, we need copy it into the correct place in the temporary Ember application under test. Node has a couple of different ways to copy files, but we’re going to be copying whole folders, and for that we can use the package fs-extra.
npm install --save-dev fs-extra
Our code so far now looks like this:
const fs = require('fs-extra')
// … some code …
// Record our current working directory; we'll need it later to grab our fixture files. Need to grab it before we run
// the setupTestHooks as this moves the working directory to the context of the test Ember instance
const thisPath = process.cwd();
// Set up the temporary folder and move the working directory (process.cwd()) to this location
setupTestHooks(this);
it('model-crud book', function() {
const dummyApp = path.join(thisPath, 'tests', 'dummy', 'app')
// pass any additional command line options in the arguments array
return emberNew()
.then(() => {
const models = ['book.form.json', 'author.js', 'book.js']
// Copy across our model and model config files
models.forEach((file) => fs.copySync(path.join(dummyApp, 'models', file), path.join(process.cwd(), 'app', 'models', file)))
// copy across the router
fs.copySync(path.join(dummyApp, 'router.js'), path.join(process.cwd(), 'app', 'router.js'))
// npm install
})
// … some more code …
})
The tricky bit here is understanding that the setupTestHooks , amongst other things, creates the temporary folder and then moves the process’ working directory to that location. There’s a few hours of my life I’m never getting back.
To see the full code, look at model-crud-test.js here.
Taking Stock
We’ve covered a lot here, and it’s time to take a breather.
The great way that the ember-cli-blueprint-test-helpers have been authored allows us to do exactly what we need by just scratching below the surface. It’s a bit disappointing this isn’t more natively supported in Ember, but maybe it’s just an opportunity for the community to step into the breach.
Next time we’ll complete the testing framework, and then follow up with actually executing some useful tests. It’s a lot of work, but re-usable once we’ve done it.
Banner image: Photo by Thirdman