Writing tests for grunt plugin turned out to be less straightforward then expected. I needed to run multiple task configurations and wanted to invoke them all by typing
Grunt normally exits after first task failure. That makes it impossible to store multiple failure scenarios inside the main project gruntfile. Running them from there would require the
Cleaner solution is to have a bunch of gruntfiles in separate directory and invoke them all from the main project gruntfile. This post explains how to do that.
The task:
There are three different ways how to write grunt plugin unit tests. Each solution has its own nodeunit file in
All three demo tests consist of three different task configurations:
Each configuration is stored in separate gruntfile inside
All three test gruntfiles look almost the same, only the
Grunt responds with following error:
This problem has two possible solutions, simple and fancy one:
Although the first "simple" solution is somewhat cleaner, demo project uses second "fancy" solution.
This is the cleaner solution. It has only two downsides:
The
Local npm repository has separate subdirectory for each npm package. All grunt plugins are supposed to have
Therefore, if we want to load all tasks of
The
Shortened gruntfile:
If you need collection plugins too, have a look at grunts task.js to see how to support them.
Node module child process has three different functions able to run command inside child process:
The first one,
Unless configured otherwise, command is run in current directory. We want it to run inside
Both stdout and stderr streams content are stored inside a buffer. Each buffer has maximum size set to 204800 and if the command produces more output,
Note: if you installed npm into tests directory (simple solution), then you need to use
Success scenario unit test:
Second node unit test runs "complete failure" scenario and then checks whether process failed as expected. Note that standard error stream is empty and warnings are printed into standard output.
Failing scenario unit test:
Third "partial failure" node unit test is almost the same as the previous one. Whole tests file is available on github.
Using fork makes sense only if you need to handle arbitrary sized stdout and stderr or if you need to customize grunt functionality. If you do not,
This chapter is split into four sub-chapters:
Our solution mimics what grunt-cli does, so it is relatively future safe. Grunt-cli is distributed separately from grunt core and therefore is less likely to change. However, if it does change, this solution will have to change too.
Running grunt from javascript requires us to:
Call grunt from javascript:
The
Call
Child process can either crash or end its work correctly:
Demo project uses following function to calls fork and to bind callbacks:
Success scenario unit test:
Tests corresponding to failure scenario are pretty much the same and can be found on github.
Following function loops through
Tests are also almost the same as those in the previous chapter. Only difference is that you have to find the grunt executable filename before doing everything else. Success scenario test looks like this:
Full success scenario test along with both failure scenarios tests are available on github.
grunt test
in main directory. Grunt normally exits after first task failure. That makes it impossible to store multiple failure scenarios inside the main project gruntfile. Running them from there would require the
--force
option, but grunt then ignores all warnings which is not optimal.Cleaner solution is to have a bunch of gruntfiles in separate directory and invoke them all from the main project gruntfile. This post explains how to do that.
Table of Contents
- Demo Project
- Running Gruntfile From Subdirectory
- The Problem
- Explanation
- Solution 1: Duplicate Npm Repository
- Solution 2: Load Grunt Tasks From Parent Directory
- Calling Gruntfile From Javascript
- Conclusion
Demo Project
Demo project is small grunt plugin with one grunt task. The task either fails with warning or prints success message into the console depending on the value ofaction
options property.The task:
grunt.registerMultiTask('plugin_tester', 'Demo grunt task.', function() { //merge supplied options with default options var options = this.options({ action: 'pass', message: 'unknown error'}); //pass or fail - depending on configured options if (options.action==='pass') { grunt.log.writeln('Plugin worked correctly passed.'); } else { grunt.warn('Plugin failed: ' + options.message); } });
test
directory and is explained in this post:- plugin_exec_test.js - the most practical solution,
- plugin_fork_test.js - solves rare edge case where previous solution fails,
- plugin_spawn_test.js - possible, but least practical.
All three demo tests consist of three different task configurations:
// Success scenario options: { action: 'pass' } // Fail with "complete failure" message options: { action: 'fail', message: 'complete failure' } //Fail with "partial failure" message options: { action: 'fail', message: 'partial failure' }
Each configuration is stored in separate gruntfile inside
test
directory. For example, success scenario stored inside gruntfile-pass.js
file looks like this:grunt.initConfig({ // prove that npm plugin works too jshint: { all: [ 'gruntfile-pass.js' ] }, // Configuration to be run (and then tested). plugin_tester: { pass: { options: { action: 'pass' } } } }); // Load this plugin's task(s). grunt.loadTasks('./../tasks'); // next line does not work - grunt requires locally installed plugins grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.registerTask('default', ['plugin_tester', 'jshint']);
options
object of plugin_tester
target changes. Running Gruntfile From Subdirectory
Our test gruntfiles are stored intest
subdirectory and grunt does not handle such situation well. This chapter explains what the problem is and shows two ways how to solve it.The Problem
To see the problem, go to the demo project directory and run following command:grunt --gruntfile test/gruntfile-problem.js
Local Npm module "grunt-contrib-jshint" not found. Is it installed? Warning: Task "jshint" not found. Use --force to continue. Aborted due to warnings.
Explanation
Grunt assumes that grunfile and node_modules repository are stored in the same directory. While node.jsrequire
function searches all parent directories for required module, grunts loadNpmTasks
does not. This problem has two possible solutions, simple and fancy one:
- create local npm repository in tests directory (simple),
- make grunt load tasks from parent directories (fancy).
Although the first "simple" solution is somewhat cleaner, demo project uses second "fancy" solution.
Solution 1: Duplicate Npm Repository
The main idea is simple, just create another local npm repository inside the tests directory:- Copy
package.json
file intotests
directory. - Add test only dependencies into it.
- Run
npm install
command every time you run tests.
This is the cleaner solution. It has only two downsides:
- test dependencies have to be maintained separately,
- all plugin dependencies have to be installed in two places.
Solution 2: Load Grunt Tasks From Parent Directory
The other solution is to force grunt to load tasks from npm repository stored inside another directory.Grunt Plugin Loading
Grunt has two methods able to load plugins:loadTasks('directory-name')
- loads all tasks inside a directory,loadNpmTasks('plugin-name')
- loads all tasks defined by a plugin.
The
loadNpmTasks
function assumes fixed directory structure of both grunt plugin and modules repository. It guess name of directory where tasks should be stored and then calls loadTasks('directory-name')
function. Local npm repository has separate subdirectory for each npm package. All grunt plugins are supposed to have
tasks
subdirectory and .js
files inside it are assumed to contain tasks. For example, loadNpmTasks('grunt-contrib-jshint')
call loads tasks from node_mudules/grunt-contrib-jshint/tasks
directory and is equivalent to:grunt.loadTasks('node_modules/grunt-contrib-jshint/tasks')
grunt-contrib-jshint
plugin from parent directory, we can do following: grunt.loadTasks('../node_modules/grunt-contrib-jshint/tasks')
Loop Parent Directories
More flexible solution is to climb through all parent directories until we find closest node_modules repository or reach root directory. This is implemented insidegrunt-hacks.js
module. The
loadParentNpmTasks
function loops parent directories :module.exports = new function() { this.loadParentNpmTasks = function(grunt, pluginName) { var oldDirectory='', climb='', directory, content; // search for the right directory directory = climb+'node_modules/'+ pluginName; while (continueClimbing(grunt, oldDirectory, directory)) { climb += '../'; oldDirectory = directory; directory = climb+'node_modules/'+ pluginName; } // load tasks or return an error if (grunt.file.exists(directory)) { grunt.loadTasks(directory+'/tasks'); } else { grunt.fail.warn('Tasks plugin ' + pluginName + ' was not found.'); } } function continueClimbing(grunt, oldDirectory, directory) { return !grunt.file.exists(directory) && !grunt.file.arePathsEquivalent(oldDirectory, directory); } }();
Modified Gruntfile
Finally, we need to replace the usualgrunt.loadNpmTasks('grunt-contrib-jshint')
call in the gruntfile by following:var loader = require("./grunt-hacks.js"); loader.loadParentNpmTasks(grunt, 'grunt-contrib-jshint');
Shortened gruntfile:
module.exports = function(grunt) { var loader = require("./grunt-hacks.js"); grunt.initConfig({ jshint: { /* ... */ }, plugin_tester: { /* ... */ } }); grunt.loadTasks('./../tasks'); loader.loadParentNpmTasks(grunt, 'grunt-contrib-jshint'); };
Disadvantages
This solution has two disadvantages:- It does not deal with collection plugins.
- If grunt ever changes expected structure of grunt plugins, you will have to modify the solution.
If you need collection plugins too, have a look at grunts task.js to see how to support them.
Calling Gruntfile From Javascript
Second thing we need to do is to invoke the gruntfile from javascript. The only complication is that grunt exits whole process on task failure. Therefore, we need to call it from child process.Node module child process has three different functions able to run command inside child process:
exec
- executes command on command line,spawn
- differently executes command on command line,fork
- runs node module in child process.
The first one,
exec
, is easiest to use and is explained in the first subchapter. Second subchapter shows how to use fork and why it is less optimal then exec. Third subchapter is about spawn.Exec
Exec runs command line command inside a child process. You can specify in which directory to run it, set up environment variables, set timeout after which the command will be killed and so on. When the command finishes its run, exec calls callback and passes it stdout stream, stderr streams and error if the command crashed.Unless configured otherwise, command is run in current directory. We want it to run inside
tests
subdirectory, so we have to specify cwd
property of options object: {cwd: 'tests/'}
.Both stdout and stderr streams content are stored inside a buffer. Each buffer has maximum size set to 204800 and if the command produces more output,
exec
call will crash. That amount is enough for our small task. If you need more you have to set maxBuffer
options property.Call Exec
Following code snippet shows how to run the gruntfile from exec. The function is asynchronous and callswhenDoneCallback
after all is done:var cp = require("child_process"); function callGruntfile(filename, whenDoneCallback) { var command, options; command = "grunt --gruntfile "+filename+" --no-color"; options = {cwd: 'test/'}; cp.exec(command, options, whenDoneCallback); }
callNpmInstallAndGruntfile
function instead of callGruntfile
:function callNpmInstallAndGruntfile(filename, whenDoneCallback) { var command, options; command = "npm install"; options = {cwd: 'test/'}; cp.exec(command, {}, function(error, stdout, stderr) { callGruntfile(filename, whenDoneCallback); }); }
Unit Tests
First node unit test runs success scenario and then checks whether process finished without failure, whether standard output contains expected message and whether standard error is empty.Success scenario unit test:
pass: function(test) { test.expect(3); callGruntfile('gruntfile-pass.js', function (error, stdout, stderr) { test.equal(error, null, "Command should not fail."); test.equal(stderr, '', "Standard error stream should be empty."); var stdoutOk = contains(stdout, 'Plugin worked correctly.'); test.ok(stdoutOk, "Missing stdout message."); test.done(); }); },
Failing scenario unit test:
fail_1: function(test) { test.expect(3); var gFile = 'gruntfile-fail-complete.js'; callGruntfile(gFile, function (error, stdout, stderr) { test.equal(error, null, "Command should have failed."); test.equal(error.message, 'Command failed: ', "Wrong error message."); test.equal(stderr, '', "Non empty stderr."); var stdoutOk = containsWarning(stdout, 'complete failure'); test.ok(stdoutOk, "Missing stdout message."); test.done(); }); }
Disadvantages
Disadvantage:- Maximum buffer size must be set in advance.
Fork
Fork runs node.js module inside child process and is equivalent to callingnode <module-name>
on command line. Fork uses callbacks to send standard output and standard error to caller. Both callbacks can be called many times and caller obtains child process outputs in pieces.Using fork makes sense only if you need to handle arbitrary sized stdout and stderr or if you need to customize grunt functionality. If you do not,
exec
is easier to use.This chapter is split into four sub-chapters:
- call grunt from javascript,
- read command line arguments inside node module,
- start node module inside a child process,
- write unit tests.
Call Grunt
Grunt was not meant to be called programatically. It does not expose "public" API and does not document it.Our solution mimics what grunt-cli does, so it is relatively future safe. Grunt-cli is distributed separately from grunt core and therefore is less likely to change. However, if it does change, this solution will have to change too.
Running grunt from javascript requires us to:
- separate gruntfile name from its path,
- change active directory,
- call grunts
tasks
function.
Call grunt from javascript:
this.runGruntfile = function(filename) { var grunt = require('grunt'), path = require('path'), directory, filename; // split filename into directory and file directory = path.dirname(filename); filename = path.basename(filename); //change directory process.chdir(directory); //call grunt grunt.tasks(['default'], {gruntfile:filename, color:false}, function() { console.log('done'); }); };
Module Arguments
The module will be called from command line. Node keeps command line arguments insideprocess.argv
array:module.exports = new function() { var filename, directory; this.runGruntfile = function(filename) { /* ... */ }; //get first command line argument filename = process.argv[2]; this.runGruntfile(filename); }();
Call Fork
Fork has three arguments: path to module, array with command line arguments and options object. Callmodule.js
with tests/Gruntfile-1.js
parameter:child = cp.fork('./module.js', ['tests/Gruntfile-1.js'], {silent: true})
silent: true
option makes stdout and stderr of the returned child
process available inside the parent. If it is set to true, returned object provides access to stdout
and stderr
streams of the caller. Call
on('data', callback)
on each stream. Passed callback will be called each time the child process sends something to the stream:child.stdout.on('data', function (data) { console.log('stdout: ' + data); // handle piece of stdout }); child.stderr.on('data', function (data) { console.log('stderr: ' + data); // handle piece of stderr });
Child process can either crash or end its work correctly:
child.on('error', function(error){ // handle child crash console.log('error: ' + error); }); child.on('exit', function (code, signal) { // this is called after child process ended console.log('child process exited with code ' + code); });
Demo project uses following function to calls fork and to bind callbacks:
/** * callbacks: onProcessError(error), onProcessExit(code, signal), onStdout(data), onStderr(data) */ function callGruntfile(filename, callbacks) { var comArg, options, child; callbacks = callbacks || {}; child = cp.fork('./test/call-grunt.js', [filename], {silent: true}); if (callbacks.onProcessError) { child.on("error", callbacks.onProcessError); } if (callbacks.onProcessExit) { child.on("exit", callbacks.onProcessExit); } if (callbacks.onStdout) { child.stdout.on('data', callbacks.onStdout); } if (callbacks.onStderr) { child.stderr.on('data', callbacks.onStderr); } }
Write Tests
Each unit test calls thecallGruntfile
function. Callbacks search for expected content inside the standard output stream, check whether exit code was correct, fail when something shows up on error stream or fail if fork call returns an error.Success scenario unit test:
pass: function(test) { var wasPassMessage = false, callbacks; test.expect(2); callbacks = { onProcessError: function(error) { test.ok(false, "Unexpected error: " + error); test.done(); }, onProcessExit: function(code, signal) { test.equal(code, 0, "Exit code should have been 0"); test.ok(wasPassMessage, "Pass message was never sent "); test.done(); }, onStdout: function(data) { if (contains(data, 'Plugin worked correctly.')) { wasPassMessage = true; } }, onStderr: function(data) { test.ok(false, "Stderr should have been empty: " + data); } }; callGruntfile('test/gruntfile-pass.js', callbacks); }
Disadvantages
Disadvantages:- Used grunt function does not belong to official API.
- Child process output streams are available in chunks instead of one big block.
Spawn
Spawn is a cross between fork and exec. Similarly to exec, spawn is able to run an executable file and pass it command line arguments. Child process output streams are treated the same way as in fork. They are send to parent in pieces via callbacks. Therefore, exactly as with fork, using spawn makes sense only if you need arbitrary sized stdout or stderr.The Problem
The main problem with spawn happens on windows. The name of command to be run must be specified exactly. If you call spawn with an argumentgrunt
, spawn expects executable filename without suffix. Real grunt executable grunt.cmd
will not be found. Otherwise said, spawn
ignores windows environment variable PATHEXT.Looping Suffixes
If you want to callgrunt
from spawn
, you will need to do one of the following things:- use different code for windows and for linux or
- read
PATHEXT
from environment and loop through it until you find the right suffix.
Following function loops through
PATHEXT
and passes the right filename to the callback:function findGruntFilename(callback) { var command = "grunt", options, extensionsStr, extensions, i, child, onErrorFnc, hasRightExtension = false; onErrorFnc = function(data) { if (data.message!=="spawn ENOENT"){ grunt.warn("Unexpected error on spawn " +extensions[i]+ " error: " + data); } }; function tryExtension(extension) { var child = cp.spawn(command + extension, ['--version']); child.on("error", onErrorFnc); child.on("exit", function(code, signal) { hasRightExtension = true; callback(command + extension); }); } extensionsStr = process.env.PATHEXT || ''; extensions = [''].concat(extensionsStr.split(';')); for (i=0; !hasRightExtension && i<extensions.length;i++) { tryExtension(extensions[i]); } }
Write Tests
Once you have grunt command name, you are ready to callspawn
. Spawn fires exactly the same events as fork, so callGruntfile
accepts exactly the same callbacks object and binds its properties to child process events:function callGruntfile(command, filename, callbacks) { var comArg, options, child; callbacks = callbacks || {}; comArg = ["--gruntfile", filename, "--no-color"]; options = {cwd: 'test/'}; child = cp.spawn(command, comArg, options); if (callbacks.onProcessError) { child.on("error", callbacks.onProcessError); } /* ... callbacks binding exactly as in fork ...*/ }
pass: function(test) { var wasPassMessage = false; test.expect(2); findGruntFilename(function(gruntCommand){ var callbacks = { /* ... callbacks look exactly the same way as in fork ... */ }; callGruntfile(gruntCommand, 'gruntfile-pass.js', callbacks); }); }
Disadvantages
Disadvantages:- Spawn ignores
PATHEXT
suffixes, custom code to handle it is needed. - Child process output streams are available in chunks instead of one big block.
Conclusion
There are three ways how to test grunt plugin from inside gruntfile. Unless you have very strong reason not to, useexec
.
115 comments:
Your good knowledge and kindness in playing with all the pieces were very useful. I don’t know what I would have done if I had not encountered such a step like this.
Block Chain Training in chennai
Block Chain Training in annanagar
Block Chain Training in pune
Block Chain Training in velachery
It’s great to come across a blog every once in a while that isn’t the same out of date rehashed material. Fantastic read.
Digital Marketing Training in Mumbai
Six Sigma Training in Dubai
Six Sigma Abu Dhabi
SEE MORE ARTICLE IN HERE. CLICK IN HERE
Wow it is really wonderful and awesome thus it is very much useful for me to understand many concepts and helped me a lot. it is really explainable very well and i got more information from your blog.
rpa training in chennai | best rpa training in chennai | rpa training in chennai | rpa training in bangalore
rpa training in pune | rpa online training
Very nice post here and thanks for it .I always like and such a super contents of these post.Excellent and very cool idea and great content of different kinds of the valuable information's.
Good discussion. Thank you.
Anexas
Six Sigma Training in Abu Dhabi
Six Sigma Training in Dammam
Six Sigma Training in Riyadh
Good Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.
Data Science training in kalyan nagar | Data Science training in OMR
Data Science training in chennai | Data science training in velachery
Data science training in tambaram | Data science training in jaya nagar
I appreciate your efforts because it conveys the message of what you are trying to say. It's a great skill to make even the person who doesn't know about the subject could able to understand the subject . Your blogs are understandable and also elaborately described. I hope to read more and more interesting articles from your blog. All the best.
java training in chennai | java training in bangalore
java online training | java training in pune
Good Post, I am a big believer in posting comments on sites to let the blog writers know that they ve added something advantageous to the world wide web.
online Python certification course
python training in OMR
python training course in chennai
This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me..
best rpa training in chennai | rpa online training |
rpa training in chennai |
rpa training in bangalore
rpa training in pune
This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me..
Best Devops Training in pune
Thanks for sharing this pretty post, it was good and helpful. Share more like this.
Blockchain Training in Chennai
Blockchain course in Chennai
Angularjs Training in Chennai
AWS Training in Chennai
DevOps Training in Chennai
Python Training in Chennai
I believe that your blog will surely help the readers who are really in need of this vital piece of information. Waiting for your updates.
French Class in Mulund
French Coaching in Mulund
French Classes in Mulund East
French Language Classes in Mulund
French Training in Mulund
French Coaching Classes in Mulund
French Classes in Mulund West
Useful content, I have bookmarked this page for my future reference.
Appium Training in Chennai
Best Appium Training institute in Chennai
Appium Certification in Chennai
Mobile Appium Training in Chennai
Mobile Appium course in Chennai
Very creativity blog!!! I learned a lot of new things from your post. It is really a good work and your post is the knowledgeable. Waiting for your more updates...
Blue Prism Training Institute in Bangalore
Blue Prism Course in Bangalore
Blue Prism Training Bangalore
Blue Prism Classes in Bangalore
Blue Prism Course in Adyar
Blue Prism Training in Mogappair
Amazing Post . Thanks for sharing. Your style of writing is very unique. Pls keep on updating.
Spoken English Classes in Chennai
Best Spoken English Classes in Chennai
Spoken English Class in Chennai
Spoken English in Chennai
Best Spoken English Class in Chennai
English Coaching Classes in Chennai
Thanks for your contribution in sharing such a useful information. Waiting for your further updates.
Education
Technology
Nice article I was really impressed by seeing this blog, it was very interesting and it is very useful for me.
Javascript Training in Bangalore
Java script Training in Bangalore
Javascript Training Institutes in Bangalore
Advanced Java Training Institute in Bangalore
Best Institute For Java Course in Bangalore
Whoa! I’m enjoying the template/theme of this website. It’s simple, yet effective. A lot of times it’s very hard to get that “perfect balance” between superb usability and visual appeal. I must say you’ve done a very good job with this.
AWS Training in Bangalore | Amazon Web Services Training in bangalore , india
AWS Training in pune | Amazon Web Services Training in Pune, india
AWS Training in Chennai|Amazon Web Services Training in Chennai,India
aws online training and certification | amazon web services online training ,india
The blog is well written and Thanks for your information.
JAVA Training Coimbatore
JAVA Coaching Centers in Coimbatore
Best JAVA Training Institute in Coimbatore
JAVA Certification Course in Coimbatore
JAVA Training Institute in Coimbatore
I am obliged to you for sharing this piece of information here and updating us with your resourceful guidance. Hope this might benefit many learners. Keep sharing this gainful articles and continue updating us.
Angularjs Training in Chennai
Angularjs Training
Angularjs Training near me
DevOps Training in Chennai
DevOps certification Chennai
DevOps certification
Really it was an awesome article!!! It was so good to read and used to improve my knowledge as updated one, keep blogging.....
Data Science Course in Mogappair
Data Science Training in Annanagar
Data Science Training in Ambattur
Data Science Course in Tnagar
Data Science Training in Saidapet
Data Science Course in Vadapalani
Wonderful blog!!! Thanks for your information… Waiting for your upcoming data.
Ethical Hacking Course in Coimbatore
Hacking Course in Coimbatore
Ethical Hacking Training in Coimbatore
Ethical Hacking Training Institute in Coimbatore
Ethical Hacking Training
Ethical Hacking Course
Great!it is really nice blog information.after a long time i have grow through such kind of ideas.thanks for share your thoughts with us.
Selenium Courses in T nagar
Selenium Training Institutes in T nagar
Selenium Certification Training in OMR
Selenium Training in Perungudi
Great blog!!! It was very impressed to me. I like so much and keep sharing. Thank you.
Robotics Courses in Bangalore
Automation Courses in Bangalore
RPA Courses in Bangalore
Robotics Classes in Bangalore
Robotics Training in Bangalore
Automation Training in Bangalore
Well post, very useful content and I really impressed. I need more info to your blog. Keep Posting.
SEO Course in Nungambakkam
SEO Training in Saidapet
SEO Course in Tnagar
SEO Course in Omr
SEO Training in Sholinganallur
SEO Course in Navalur
Nice article. I liked very much. All the informations given by you are really helpful for my research. keep on posting your views.
Salesforce Administrator 201 Training in Chennai
Salesforce Administrator 211 Training in Chennai
Salesforce Developer 401 Training in Chennai
Cloud computing Training in Chennai
Cloud computing Training
Cloud computing Training near me
The blog which you have shared is more informative. Thanks for your information.
JAVA Training Center in Coimbatore
JAVA Training
JAVA Certification Course
JAVA Certification Training
JAVA Training Courses
Outstanding information!!! Thanks for sharing your blog with us.
Spoken English Class in Coimbatore
Best Spoken English Classes in Coimbatore
Spoken English in Coimbatore
Spoken English Classes
English Speaking Course
The information you have shared is more useful to us. Thanks for your blog.
List of Franchise Business in India
Franchise Opportunities in India with Low Investment
Best Franchise Business in India
Frenchies in India
Top Franchise in
India
Thank you for sharing your article. Great efforts put it to find the list of articles which is very useful to know, Definitely will share the same to other forums.
best openstack training in chennai | openstack course fees in chennai | openstack certification in chennai | redhat openstack training in chennai
Well done! This is really powerful post and also very interesting. Thanks for your sharing and I want more updates from your blog.
Digital Marketing Training in Aminjikarai
Digital Marketing Training in vadapalani
Digital Marketing Course in Chennai
Digital Marketing Training in Omr
Digital Marketing Training in Kelambakkam
Digital Marketing Training in Karappakkam
I am really enjoying reading your well-written articles. It looks like you spend a lot of effort and time on your blog. I have bookmarked it and I am looking forward to reading new articles. Keep up the good work.
SEO Training in Chennai
Digital Marketing Chennai
Digital Marketing Courses in Chennai
Digital marketing courses
SEO Institutes in Chennai
SEO Course Chennai
Outstanding blog thanks for sharing such wonderful blog with us ,after long time came across such knowlegeble blog. keep sharing such informative blog with us.
machine learning classroom training in chennai
machine learning certification in chennai
top institutes for machine learning in chennai
Android training in velachery
PMP training in chennai
Thanks for your post. This is excellent information. The list of your blogs is very helpful for those who want to learn, It is amazing!!! You have been helping many application.
best selenium training in chennai | best selenium training institute in chennai selenium training in chennai | best selenium training in chennai | selenium training in Velachery | selenium training in chennai omr | quora selenium training in chennai | selenium testing course fees | java and selenium training in chennai | best selenium training institute in chennai | best selenium training center in chennai
Such a Great Article!! I learned something new from your blog. Amazing stuff. I would like to follow your blog frequently. Keep Rocking!!
Blue Prism training in chennai | Best Blue Prism Training Institute in Chennai
Thank you so much for your information,its very useful and helpful to me.Keep updating and sharing. Thank you.
RPA training in chennai | UiPath training in chennai | rpa course in chennai | Best UiPath Training in chennai
Hi, Thanks a lot for your explanation which is really nice. I have read all your posts here. It is amazing!!!
Keeps the users interest in the website, and keep on sharing more, To know more about our service:
Please free to call us @ +91 9884412301 / 9600112302
Openstack course training in Chennai | best Openstack course in Chennai | best Openstack certification training in Chennai | Openstack certification course in Chennai | openstack training in chennai omr | openstack training in chennai velachery | openstack training in Chennai | openstack course fees in Chennai | openstack certification training in Chennai | best openstack training in Chennai | openstack certification in Chennai
Thanks for the post very impressive
Best Blockchain training in chennai
Thank you so much for the detailed article
Such an excellent and interesting blog, do post like this more with more information, this was very useful, Thank you.
Aviation Courses in Chennai
cabin crew training chennai
airline academy in chennai
airport ground staff training courses in chennai
medical coding course in chennai
fashion designing institute in chennai
best interior design courses in chennai
Thanks for your great and helpful presentation I like your good service. I always appreciate your post. That is very interesting I love reading and I am always searching for informative information like this.iot training institutes in chennai | industrial iot training chennai | iot course fees in chennai | iot certification courses in chennai
I couldn’t resist commenting. Exceptionally well written!
Boots Opticians
CiCi's Pizza
TellCulvers
TellDunkin
Panda Express Feedback from these surveys you can win coupons, Free Fresh Frozen Custard from culvers & Free Donuts within minutes. give your feedback and grab these offers
You are an amazing writer. The content is extra-ordinary. Looking for such a masterpiece. Thanks for sharing.
IoT Training in Chennai
IoT courses in Chennai
IoT Courses
IoT Training in Porur
IoT Training in Adyar
Thanks for sharing this valuable information and we collected some information from this blog.
Javascript Training in Delhi
A bewildering web journal I visit this blog, it's unfathomably heavenly. Oddly, in this present blog's substance made the purpose of actuality and reasonable. The substance of data is informative
Oracle Fusion Financials Online Training
Oracle Fusion HCM Online Training
Oracle Fusion SCM Online Training
Tremendous effort. Mind-blowing, Extra-ordinary Post. Your post is highly inspirational. Waiting for your future posts.
Data Analytics Courses in Chennai
Big Data Analytics Courses in Chennai
Data Analytics Courses
Big Data Analytics Training
Big Data Analytics Courses
Data Analytics Certification
Data Analytics Courses in OMR
Data Analytics Courses in Tambaram
Nice post. Thanks for sharing! I want people to know just how good this information is in your article. It’s interesting content and Great work.
Thanks & Regards,
VRIT Professionals,
No.1 Leading Web Designing Training Institute In Chennai.
And also those who are looking for
Web Designing Training Institute in Chennai
SEO Training Institute in Chennai
Photoshop Training Institute in Chennai
PHP & Mysql Training Institute in Chennai
Android Training Institute in Chennai
Great Post. Extra-ordinary work. Looking for your future blogs.
Informatica Training in Chennai
Informatica Training Center Chennai
Informatica course in Chennai
Informatica Training center in Chennai
Informatica Training chennai
Informatica Training institutes in Chennai
Informatica Training in OMR
Informatica Training in Porur
Brilliant blog I visit this blog it's incredibly awesome. Curiously, in this blog content formed doubtlessly and sensible. The substance of information is helpful.
Oracle Fusion HCM Online Training
Oracle Fusion SCM Online Training
Oracle Fusion Financials Online Training
Oracle Fusion HCM Training In Hyderabad
Oracle Fusion SCM Training In Hyderabad
Oracle Fusion Financials Training In Hyderabad
oracle fusion financials classroom training
Oracle Fusion HCM Classroom Training
oracle cpq online training / Oracle CPQ Class Room Training
Oracle Taleo Online Training
An overwhelming web journal I visit this blog, it's unfathomably amazing. Unusually, in this present blog's substance made inspiration driving truth and reasonable. The substance of data is enlightening
Oracle Fusion Financials Online Training
Oracle Fusion HCM Online Training
Oracle Fusion SCM Online Training
If you're looking for a date but not sure whether the dating site is a safe place, check our https://wizzlove.com/reviews/victoriamilan-review review.
Apabila anda tidak memperhatikan nilai kartu yang anda miliki tersebut, maka tentukan taruhan dengan memperhatikan nilai kartu terlebih dahulu. Dengan begitu, anda tidak akan mendapatkan kerugian yang besar.
asikqq
http://dewaqqq.club/
http://sumoqq.today/
interqq
pionpoker
bandar ceme terbaik
betgratis
paito warna terlengkap
forum prediksi
An overwhelming web journal I visit this blog, it's unfathomably amazing. Unusually, in this present blog's substance made inspiration driving truth and reasonable. The substance of data is enlightening
Oracle Fusion Financials Online Training
Oracle Fusion HCM Online Training
Oracle Fusion SCM Online Training
Hiii...Thanks for sharing Great info...Nice post...Keep move on...
Best Python Training in Hyderabad
Come on... Keep posting, Lady... I was a while since your last post. Don't make me sad (:
Good Information
Yaaron Studios is one of the rapidly growing editing studios in Hyderabad. We are the best Video Editing services in Hyderabad. We provides best graphic works like logo reveals, corporate presentation Etc. And also we gives the best Outdoor/Indoor shoots and Ad Making services.
video editing studios in hyderabad
short film editors in hyderabad
corporate video editing studio in hyderabad
ad making company in hyderabad
Nice Post
"Sanjary Academy provides excellent training for Piping design course. Best Piping Design Training Institute in Hyderabad,
Telangana. We have offer professional Engineering Course like Piping Design Course,QA / QC Course,document Controller
course,pressure Vessel Design Course, Welding Inspector Course, Quality Management Course, #Safety officer course."
Piping Design Course in India
Piping Design Course in Hyderabad
QA / QC Course
QA / QC Course in india
QA / QC Course in Hyderabad
Document Controller course
Pressure Vessel Design Course
Welding Inspector Course
Quality Management Course
Quality Management Course in india
Safety officer course
Thanks for sharing such a great blog Keep posting.
data selling companies
b2b database providers in India
database provider
indian business directory database excel
top 10 mnc in gurgaon
mnc companies in delhi gurgaon
sales automation tools
sales automation crm
Really a awesome blog for the freshers. Thanks for posting the information.
stamp online india
paper bags online india
laptop on rent price
laptop hire in chennai
company registration fees in india
company registration consultants in india
I have been reading for the past two days about your blogs and topics, still on fetching! Wondering about your words on each line was massively effective. Techno-based information has been fetched in each of your topics. Sure it will enhance and fill the queries of the public needs. Feeling so glad about your article. Thanks…!
magento training course in chennai
magento training institute in chennai
magento 2 training in chennai
magento development training
magento 2 course
magento developer training
I like your post very much. It is very much useful for my research. I hope you to share more info about this. Keep posting!!
aws Training in Bangalore
python Training in Bangalore
hadoop Training in Bangalore
angular js Training in Bangalore
bigdata analytics Training in Bangalore
python Training in Bangalore
aws Training in Bangalore
I am really happy with your blog because your article is very unique and powerful for new reader.
aws Training in Bangalore
python Training in Bangalore
hadoop Training in Bangalore
angular js Training in Bangalore
bigdata analytics Training in Bangalore
python Training in Bangalore
aws Training in Bangalore
Thank you for sharing information. Wonderful blog & good post.
aws Training in Bangalore
python Training in Bangalore
hadoop Training in Bangalore
angular js Training in Bangalore
bigdata analytics Training in Bangalore
python Training in Bangalore
aws Training in Bangalore
Nice post. It is really interesting. Thanks for sharing the post!
Fridge Online Shopping | Refrigerator Online Shopping
Refrigerator Price Online | Online Fridge Price
Washing Machine Sale | Washing Machine Online Offers
Mobile Phone Offers Online | Buy Mobile Online
Sathya Online Shopping
Great blog and i recently visit this post and i am very impressed. Thank you...
Oracle Training in Chennai
Oracle Certifications
Social Media Marketing Courses in Chennai
Tableau Training in Chennai
Primavera Training in Chennai
Unix Training in Chennai
Graphic Design Courses in Chennai
Pega Training in Chennai
Oracle DBA Training in Chennai
Power BI Training in Chennai
Oracle Training in Tambaram
Wow! this is Amazing! Do you know your hidden name meaning ? Click here to find your hidden name meaning
I have been reading for the past two days about your blogs and topics, still on fetching! Wondering about your words on each line was massively effective.
php online training in chennai
php programming center in chennai
php class in chennnai
php certification course
php developer training institution chennai
php training in chennnai
php mysql course in chennai
php institute in chennnai
php course in chennnai
php training with placement in chennnai
php developer course
Nice post. Thanks for sharing! I want people to know just how good this information is in your article. It’s interesting content and Great work.
appium online training
appium training centres in chennai
best appium training institute in chennnai
apppium course
mobile appium in chennnai
mobile training in chennnai
appium training institute in chennnai
Wow! this is Amazing! Do you know your hidden name meaning ? Click here to find your hidden name meaning
Such a wonderful article and I feel that it is best to write more on this topic. Thank you so much because i learn a lot of ideas about it. Keep posting...
Digital Marketing Course In Kolkata
Web Design Course In Kolkata
SEO Course In Kolkata
Wow! this is Amazing! Do you know your hidden name meaning ? Click here to find your hidden name meaning
Top Chauffeur service in Melbourne
Whether you need a last minute chauffeur car or a planned vehicle for your outing, book with us and get served on time. With well-mannered chauffeurs and finest vehicles, we arrange to pick and drop our customers with great punctuality. A hassle-free traveling experience waits at Silver Executive Cab for every customer
Best Immigration Lawyer in Brampton
Best Real Estate Lawyer in Toronto
We specialize in assisting clients in the areas of Real Estate, Corporate and Commercial, Wills and Estates, Immigration and more.
BTorres Law Office offers an innovative and results-oriented approach to building solid working relationships with all of our valued clients.
Our shared values, and our commitment to the highest standards of the practice of law is our top priority.
Effective blog with a lot of information. I just Shared you the link below for Courses .They really provide good level of training and Placement,I just Had Android Classes in this institute , Just Check This Link You can get it more information about the Android course.
Java training in chennai | Java training in annanagar | Java training in omr | Java training in porur | Java training in tambaram | Java training in velachery
Thanks for the informative article About Java. This is one of the best resources I have found in quite some time. Nicely written and great info. I really cannot thank you enough for sharing.
Java training in chennai | Java training in annanagar | Java training in omr | Java training in porur | Java training in tambaram | Java training in velachery
The post you wrote which is full of informative content. I Like it very much. Keep on posting!!thanks a lot
Ai & Artificial Intelligence Course in Chennai
PHP Training in Chennai
Ethical Hacking Course in Chennai Blue Prism Training in Chennai
UiPath Training in Chennai
Absolutely fantastic posting! Lots of useful information and inspiration, both of which we all need!Relay appreciate your work.
Software Testing Services
QA Testing Services
Software Testing Services in USA
Software Testing Companies in USA
Software Testing Company
Software Testing and Quality Assurance
QA Testing Companies
Independent Software Testing Companies in USA
Independent Software Testing Services
Software Testing Companies in India
Software Testing Companies in Dubai
Functional Testing Services
QA Software Testing Services
QA Testing Companies in USA
Thank you for sharing such a great blog post with us this blog is really informative and useful, keep it up..
Software Testing Services
Software Testing Services in India
Software Testing Companies in India
Software Testing Services in USA
Software Testing Companies in USA
Software Testing Companies
Software Testing Services Company
Software Testing Companies in New York
That is nice article from you , this is informative stuff . Hope more articles from you . I also want to share some information about devops training in pune
Bài viết rất hay: Chúng tôi chuyên cung cấp các sản phẩm chất lượng
Lều xông hơi tại nhà giá rẻ
Lều xông hơi mua ở đâu
Lều xông hơi giá bao nhiêu
Bài viết rất hay: Chúng tôi chuyên cung cấp các sản phẩm chất lượng
Giảo cổ lam giảm mỡ hiệu quả
Tìm hiểu giảo cổ lam khô hiện nay
Tác dụng thần kỳ của giảo cổ lam khô
Thanks for Sharing This Article.It is very so much valuable content. I hope these Commenting lists will help to my website
top blockchain online training
Thank you for sharing.
Data Science Online Training
Python Online Training
Salesforce Online Training
Nice post!
Worried About QuickBooks Error ?Get in touch with QuickBooks expert for instant solution.
Click Here to know how to fix QuickBooks Error 1723
Dial on QuickBooks Toll-free Number +1-855-977-7463.
Thanks for Sharing This Article.It is very so much valuable content. I hope these Commenting lists will help to my website
blockchain online training
best blockchain online training
top blockchain online training
Nice Blog !
Our experts at QuickBooks Phone Number are ready to give you immediate support for all QuickBooks errors in this difficult time.
I think this is the best article today. Thanks for taking your own time to discuss this topic, I feel happy about that curiosity has increased to learn more about this topic. Keep sharing your information regularly for my future reference.Excellent blog admin. This is what I have looked. Check out the following links for Software testing companies USA
Test automation software
Best automated testing software
Mobile app testing services
Load testing services
Đặt vé máy bay tại Aivivu, tham khảo
vé máy bay đi Mỹ hạng thương gia
vé máy bay hà nội sài gòn vietjet
vé máy bay đà nẵng hà nội hôm nay
vé máy bay đi nha trang tháng 7
vé máy bay đi quy nhơn tháng 2
dịch vụ taxi sân bay
combo phú quốc 4 ngày 3 đêm vinpearl
Nice Post.. really helpful to clear my small concepts. thanks :)Keep doing more, waiting to read your next blog security testing services. Visit here for more information about Vulnerability assessment services and penetration testing services.
selenium online trainings from India
seleniumonline courses
Thanks for sharing a useful knowledge-sharing blog.
It helps in understanding and increase in knowledge.
Best Regards,
prestashop
magento
prestashop addons
prestashop modules
presta addons
magento extensions
prestashop schema rich snippets
prestashop seo module
prestashop custom html js
prestashop hreflang tags
prestashop product tabs
prestashop social login
prestashop banners
prestashop social media
prestashop open graph
prestashop twitter card
magento geolocation redirect
prestashop geolocation
prestashop popup
prestashop seo url
prestashop geoip
Mua vé máy bay tại Aivivu, tham khảo
vé máy bay đi Mỹ bao nhiêu tiền
mua vé máy bay về vn từ mỹ
mua ve may bay gia re tu duc ve viet nam
dịch vụ vé máy bay tại nga
giá vé máy bay từ anh về hà nội
vé máy bay từ pháp về việt nam
khách sạn cách ly ở đà nẵng
Hey! Excellent work. Being a QuickBooks user, if you are struggling with any issue, then dial QuickBooks Customer Service Phone Number Our team at QuickBooks will provide you with the best technical solutions for QuickBooks problems.
Django store image in database
Python MySQL Tutorial
Node js Data types
PHP multiple choice questions and answers
PHP html to pdf
Javascript add rows to table dynamically
Insert in database without page refresh php
Django insert data into database
Python convert xml to csv
Send email to multiple recipients Python
Hey! What a wonderful blog. I loved your blog. QuickBooks is the best accounting software; however, it has lots of bugs like QuickBooks for MAC Support . To fix such issues, you can contact experts via QuickBooks Support Phone Number (855)963-5959.
Awesome..You have clearly explained …Its very useful for me to know about new things..Keep on blogging..
internship for web development | internship in electrical engineering | mini project topics for it 3rd year | online internship with certificate | final year project for cse
nice blog. We provide quickbooks customer service you can contact us on call. 877-603-0806
Awesome bolg.if you looking for a best quickbook customer service you can contact us on phone call.+1 888-272-4881
awesome bolg.if you need best quickbook support service. contact to quickbook service team.+1 888-471-2380
Such a nice blog, thanks for sharing with everyone, if you face trouble resolving QuickBooks Errors, Contact:Quickbooks customer service number.+18554442233
nice blog. you are looking for a best custumer service Quickbooks support serviceyou can contact us at.+13464148256
Nice content!!
if you are looking for a best Quickbook support serviceyou can reach us at.+1 855-444-2233
Thanks for sharing such a useful post.
Web Development Services in Chennai
Your blog great. New chapter of life is yoga , Let's start to do. provide yoga , meditation basics yoga , yoga benefits , , types of yoga , - theyogainfo.com you reach us at
Wonderful blog. Lets starts the Melody morning with Surryanamaskar and complete your day. meditation basics yoga , yoga benefits , types of yoga , yoga history , health benefit yoga , yoga pose , yoga asanas - theyogainfo.com you reach us at
Wonderful blog. Lets starts the Melody morning with Surryanamaskar and complete your day. meditation basics yoga , yoga benefits , types of yoga , yogainfo- theyogainfo.com you reach us at
Thanks for sharing amazing blog. Yoga can makes the easy to control your mind ,to understand about your mind and to keep calm your mind. types of yoga , yogainfo ,theyogainfo.comyou reach us at
Thanks for sharing good blog. Yoga have the power to change your mentality that how to think ,how to control your mind and how to use it. yogainfo , yoga asanas, theyogainfo.com you reach us at
QuickBooks also offers live chat support as well as email support which allows businesses who are out of the office or at home to access QuickB's qualified help desk staff. The QuickBooks Service Number is +1 888-471-2380 and is always available 24 hours a day to serve you with all your QuickBooks questions.
When you or your company need help with QuickBooks or any other aspect of your business, dial Quickbooks Suppport Phone Number +1 866-669-5068,NJ.
The toll free number is Quickbooks Suppport Phone Number +1 855-769-6757 and you need to dial that number for your queries and errors issues.
Nice blog. If you are looking for a best knowledge of yoga Yoga, yogainfo , yoga asanas you reach us at
Thanks for posting a very sweet article. So much needed data I'll get and keep in my file. Thank u so much.
VBSPU BA 2nd Year Exam Result | VPSPU BA 3rd Year Exam Result.
Post a Comment