We usually use Vagrant to provision and manage VirtualBox virtual machines or VMs. And Vagrant only ships with VirtualBox support by default. But Vagrant can do much more. VirtualBox is just one of providers, additional providers can be added via the Vagrant plugin system. Here, we are going to use Google Compute Engine as the provider and setup a disposable development environment.
Basic steps involve:
Install Vagrant
Install vagrant-google plugin
Add SSH keys to metadata server
Add Vagrant box
Add a Vagrantfile and override the defaults from the box
Provision the machine
First, install Vagrant (exampled by Debian systems such as Ubuntu):
Installing the 'vagrant-google' plugin. This can take a few minutes...
Installed the plugin 'vagrant-google (0.2.1)'!
In order to SSH into the VM, we need to add public key to the instance which will be provisioned. In Google Compute Engine, this is done via metadata server at the project level.
Add the username to prefix the SSH key, so the Compute Engine will pick it up and create the corresponding user:
Google Cloud SDK or gcloud command should be installed already. Now it’s time to configure the machine to be provisioned.
The plugin provides a Vagrant box, which is a package format for Vagrant environments. It is not necessary to create a new box, we can just override the defaults by the Vagrantfile. So add the provider-specific box:
In Jasmine, the testing structure of setup, test, and teardown is done by using beforeEach/it+/afterEach. Global beforeEach and afterEach functions are called once before each spec in the describe.
Let’s walk through a few examples.
Before describing any example, set up a function to watch how the value changes from executing one spec to another. Object.observe is yet to be available in Firefox, but we can use something similar with Object.watch:
1
2
3
4
5
// Specific to Firefox only.
functionwatchHandler(prop, oldVal, newVal){
console.log(prop + ' changed from ' + oldVal + ' to ' + newVal);
return newVal;
}
The first example is to perform the setup without using beforeEach:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
describe('Without "beforeEach"', function(){
var obj = {};
var prop = 'foo1';
obj[prop] = 0;
obj.watch(prop, watchHandler); // Firefox only
// Setup
obj[prop] += 1;
it(prop + ' should be equal to 1', function(){
expect(obj[prop]).toBe(1);
});
it(prop + ' should not be equal to 2', function(){
expect(obj[prop]).not.toBe(2);
});
});
The obj property foo1 was changed once:
1
LOG: 'foo1 changed from 0 to 1'
If using beforeEach to perform setup before each spec:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe('With "beforeEach"', function(){
var obj = {};
var prop = 'foo2';
obj[prop] = 0;
obj.watch(prop, watchHandler); // Firefox only
// Setup
beforeEach(function(){
obj[prop] += 1; // Should be invoked twice.
});
it(prop + ' should be equal to 1', function(){
expect(obj[prop]).toBe(1);
});
it(prop + ' should be equal to 2', function(){
expect(obj[prop]).toBe(2);
});
});
Then we should expect the value to be updated twice. There are two specs, and for each spec, the beforeEach will be invoked:
1
2
LOG: 'foo2 changed from 0 to 1'
LOG: 'foo2 changed from 1 to 2'
If we change the values inside a spec without using beforeEach:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
describe('Without "beforeEach" but updating in spec', function(){
var obj = {};
var prop = 'foo3';
obj[prop] = 0;
obj.watch(prop, watchHandler); // Firefox only
// Setup
obj[prop] += 1;
it(prop + ' should be equal to 1', function(){
expect(obj[prop]).toBe(1);
obj[prop] += 1;
});
it(prop + ' should be equal to 2', function(){
expect(obj[prop]).toBe(2);
});
});
The value has been incremented to 2 inside the first spec before the execution of the second spec:
1
2
LOG: 'foo3 changed from 0 to 1'
LOG: 'foo3 changed from 1 to 2'
With using beforeEach:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
describe('With "beforeEach" and updating in spec', function(){
var obj = {};
var prop = 'foo4';
obj[prop] = 0;
obj.watch(prop, watchHandler); // Firefox only
// Setup
beforeEach(function(){
obj[prop] += 1;
});
it(prop + ' should be equal to 1', function(){
expect(obj[prop]).toBe(1);
obj[prop] += 1;
});
it(prop + ' should be equal to 3', function(){
expect(obj[prop]).toBe(3);
});
});
Now, before each spec, the value was incremented, and inside the first spec, the value was also incremented:
1
2
3
LOG: 'foo4 changed from 0 to 1'
LOG: 'foo4 changed from 1 to 2'
LOG: 'foo4 changed from 2 to 3'
To ensure the value stays the same before each spec, we can use afterEach:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
describe('Reset with "afterEach"', function(){
var obj = {};
var prop = 'foo5';
obj[prop] = 0;
obj.watch(prop, watchHandler); // Firefox only
// Setup
beforeEach(function(){
obj[prop] += 1;
});
it(prop + ' should be equal to 1', function(){
expect(obj[prop]).toBe(1);
obj[prop] += 1;
});
it(prop + ' should still be equal to 1', function(){
expect(obj[prop]).toBe(1);
});
// Teardown
afterEach(function(){
obj[prop] = 0;
});
});
After each spec, the value has been reset, and before the start of next spec, the value has been incremented from original value 0 to 1 again:
1
2
3
4
5
LOG: 'foo5 changed from 0 to 1'
LOG: 'foo5 changed from 1 to 2'
LOG: 'foo5 changed from 2 to 0'
LOG: 'foo5 changed from 0 to 1'
LOG: 'foo5 changed from 1 to 0'
The locations of beforeEach and afterEach do not matter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
describe('Reverse the order of "beforeEach" and "afterEach"', function(){
var obj = {};
var prop = 'foo6';
obj[prop] = 0;
obj.watch(prop, watchHandler); // Firefox only
// Teardown
afterEach(function(){
obj[prop] = 0;
});
it(prop + ' should be equal to 1', function(){
expect(obj[prop]).toBe(1);
obj[prop] += 1;
});
it(prop + ' should still be equal to 1', function(){
expect(obj[prop]).toBe(1);
});
// Setup
beforeEach(function(){
obj[prop] += 1;
});
});
Here we switch the setup and teardown, but the result is the same:
1
2
3
4
5
LOG: 'foo6 changed from 0 to 1'
LOG: 'foo6 changed from 1 to 2'
LOG: 'foo6 changed from 2 to 0'
LOG: 'foo6 changed from 0 to 1'
LOG: 'foo6 changed from 1 to 0'
Because the content of the suite is read before any of specs is executed.
Asynchronous setup, test, and teardown:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
describe('Async with "beforeEach" and "afterEach"', function(){
var obj = {};
var prop = 'foo7';
obj[prop] = 0;
obj.watch(prop, watchHandler); // Firefox only
// Setup
beforeEach(function(done){
setTimeout(function(){
obj[prop] += 1;
done();
}, 100);
});
it(prop + ' should be equal to 1', function(done){
setTimeout(function(){
expect(obj[prop]).toBe(1);
obj[prop] += 1;
done();
}, 100);
});
it(prop + ' should still be equal to 1', function(done){