Jasmine is a BDD (behavior driven development) testing framework that can be used to write JavaScript unit tests and is currently the most popular framework choice for testing angular components. In a nutshell it helps us to create suites of tests that each state a behavior we expect to occur in our program’s logic for it to run correctly. You’ll often see Jasmine test descriptions that state thing’s like ‘it should’ do this, ‘it can’ do that, ‘it cant do that’, etc each describing what a small piece of logic in our program does.
Below is a small code snippet containing the obligatory ‘add 2 numbers’ test to illustrate a super simple example of a suite containing one spec that is uses a matcher to assert an expectation.
describe('Simple math tests', function() { it('Should add 2 numbers', function() { var calculator = new Calculator(); expect(calculator.add(2, 2)).toBe(4); }); });
In more realistic coding scenarios we’ll also need to use spies which allow us to provide mock implementations that return results to setup particular test expectations and also let us know if a given function has been called or not.
There are several ways to utilize spies with Jasmine including:
SpyOn an Existing Object
To spy on an object, is to create a stand-in for particular function in order to assert whether or not it has been called and also creates opportunity to arrange for a particular results to be returned in order to facilitate a test sceanrio. It’s important as well that that this stand-in function is called instead of the real code so that our test is isolated from interacting with other systems through network requests, database requests, file io, etc.
In the example below I’m creating a stand-in function called ‘createAccount’ that will be called and then testing that our manager object has called that function when its ‘addUser’ function is called:
Note: if instead of just calling the stand-in function that does nothing you really need the underlying function to be called as well to complete your test scenario you can call spyOn(…).andCallThrough() which will execute both the stand-in and the real function.
describe('User account creation tests', function() {
it('Should create a new user account', function() {
var accountService = new AccountService();
var accountManager = new AccountManager(accountService);
spyOn(accountService, 'createAccount');
accountManager.addUser('John', 'Doe');
expect(accountService.createAccount).toHaveBeenCalled();
});
.
.
.
});
Create a Spy Object
Another facility Jasmine provides is the ability to create completely fake stub objects that can do absolutely nothing, or can provide a fake test stub implementation.
describe('User account creation tests', function() { it('Should create a new user account', function() { var accountService = jasmine.createSpyObj('AccountService', ['userExists', 'createAccount']); var accountManager = new AccountManager(accountService); accountService.userExists.and.callFake(function(){ return true; }); spyOn(accountService, 'createAccount') expect(accountService.createAccount).not.toHaveBeenCalled(); }); . . . });
Use a Spy to Arrange an Expectation
Either by spying on or creating a spy object we can also return a specific result that is needed in order to test a specific scenario, in the example below I’m returning a result from a spy object stub function to say ‘the user name already exists, so the account should not have been created’:
describe('User account creation tests', function() { it('Should not create a new user account if the user already exists', function() { var accountService = new AccountService(); var accountManager = new AccountManager(accountService); spyOn(accountService, 'createAccount'); spyOn(accountService, 'userExists').and.returnValue(true); accountManager.addUser('John', 'Doe'); expect(accountService.createAccount).not.toHaveBeenCalled(); }); });
File structure is a very important part of creating a well organized and maintainable software project. How unit tests are woven into the code is no exception and there are a couple of obvious ways to do so:
The best way I’ve found is a way I’d read about where you use the .spec.js naming convention, adding your tests right along side the components they are testing. This makes it really easy to find unit tests for a given component as well as to see what components have or have not had tests written for them.
Example Project Structure
- myApp karma.conf.js - www - app - dashboard dashboardController.js dashboardController.spec.js dashboardService.js dashboardService.spec.js dashboardModule.js - login loginController.js loginController.spec.js . . .
One issue with creating your unit tests along side your production code in an Ionic project is that they will then be copied out to your application build folder along with the production code. Below is an example Cordova hook I’ve written however that will be used to remove .spec.js files from the build output, removing them from app. Just add a hook file under your project’s hooks -> after_prepare directory and thefollowing hook code will take care of the clean-up:
note: it requires the node ‘del’ package, so be sure to npm install that as well
#!/usr/bin/env node
var del = require('del');
var specFiles = ['platforms/ios/www/app/**/*.spec.js',
'platforms/android/assets/www/app/**/*.spec.js'];
// Output the names of the files we're about to delete on the console for diagnostic puposes
del(specFiles, {dryRun: true})
.then(function (paths) {
console.log('Spec files to be removed:\n', paths.join('\n'));
});
// Delete the test .spec.js files
del(specFiles).then(function () {
console.log("Tests have been removed from your build");
});
Anexinet is a leading professional consulting and services company, providing a broad range of services and solutions around digital disruption, analytics (and big data), and hybrid and private cloud strategies. Anexinet brings insight into how technology will impact how business decisions will be made and how our clients interact with their customers in the future.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie | Duration | Description |
---|---|---|
cookielawinfo-checbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Other uncategorized cookies are those that are being analyzed and have not been classified into a category as yet.