patternjavascriptMinor
Testing input validation and an AJAX request with Javascript and QUnit
Viewed 0 times
ajaxwithjavascriptrequestvalidationqunitinputtestingand
Problem
I have a simple web page where the user enters the name and rank of a soldier and hits submit. If the input is invalid, an appropriate error message gets displayed. Otherwise, it adds the soldier to a database and adds it to a list of current Soldiers via AJAX request.
```
//Find elements on page
var rankField = $("input[name='rank']");
var nameField = $("input[name='name']");
var submit = $("input[type='submit']");
var list = $("ul");
//attach event-handler to submit button
submit.click(function() {
var rank = rankField.val();
var name = nameField.val();
onSoldierSubmitted(rank, name);
});
function onSoldierSubmitted(rank, name) {
clearErrors();
var rankIsValid = isValidRank(rank);
var hasErrors = false;
if (!rankIsValid) {
addError('rank is invalid');
hasErrors = true;
};
if (!name) {
addError('Please enter a name for your Soldier');
hasErrors = true;
};
if (hasErrors) {return;}
appendSoldier();
};
function clearErrors() {
$('.alert-danger').empty();
};
function addError(errorText) {
var errorDiv = $('.alert-danger');
var currentError = errorDiv.html();
if (currentError) {
currentError += '';
};
errorDiv.html(currentError + errorText);
};
function isValidRank(rank) {
var ranks = ['GEN', 'LTG', 'MG', 'BG', 'COL', 'LTC', 'MAJ',
'CPT', '1LT', '2LT', 'CW5', 'CW4', 'CW3', 'CW2', 'WO1',
'SMA', 'CSM', 'SGM', '1SG', 'MSG', 'SFC', 'SSG', 'SGT',
'CPL', 'SPC', 'PFC', 'PV2', 'PVT']
normalizedRank = rank.toUpperCase();
return ranks.indexOf(normalizedRank) != -1;
};
function appendSoldier() {
$.get("/ajax/soldier",
{
rank: rankField.val(),
name: nameField.val(),
}).done(
function(data) {
soldiers = extractSoldiersFromList(data);
renderHTMLListFromData(soldiers);
}
);
clearInput();
};
function extractSoldiersFromList(data) {
var soldierData = $.parseJSON(data);
var soldiers = [];
for (var i=0; i " + soldiers[i] + "");
}
}
function clear
```
//Find elements on page
var rankField = $("input[name='rank']");
var nameField = $("input[name='name']");
var submit = $("input[type='submit']");
var list = $("ul");
//attach event-handler to submit button
submit.click(function() {
var rank = rankField.val();
var name = nameField.val();
onSoldierSubmitted(rank, name);
});
function onSoldierSubmitted(rank, name) {
clearErrors();
var rankIsValid = isValidRank(rank);
var hasErrors = false;
if (!rankIsValid) {
addError('rank is invalid');
hasErrors = true;
};
if (!name) {
addError('Please enter a name for your Soldier');
hasErrors = true;
};
if (hasErrors) {return;}
appendSoldier();
};
function clearErrors() {
$('.alert-danger').empty();
};
function addError(errorText) {
var errorDiv = $('.alert-danger');
var currentError = errorDiv.html();
if (currentError) {
currentError += '';
};
errorDiv.html(currentError + errorText);
};
function isValidRank(rank) {
var ranks = ['GEN', 'LTG', 'MG', 'BG', 'COL', 'LTC', 'MAJ',
'CPT', '1LT', '2LT', 'CW5', 'CW4', 'CW3', 'CW2', 'WO1',
'SMA', 'CSM', 'SGM', '1SG', 'MSG', 'SFC', 'SSG', 'SGT',
'CPL', 'SPC', 'PFC', 'PV2', 'PVT']
normalizedRank = rank.toUpperCase();
return ranks.indexOf(normalizedRank) != -1;
};
function appendSoldier() {
$.get("/ajax/soldier",
{
rank: rankField.val(),
name: nameField.val(),
}).done(
function(data) {
soldiers = extractSoldiersFromList(data);
renderHTMLListFromData(soldiers);
}
);
clearInput();
};
function extractSoldiersFromList(data) {
var soldierData = $.parseJSON(data);
var soldiers = [];
for (var i=0; i " + soldiers[i] + "");
}
}
function clear
Solution
My reasoning here is that unit tests aren't supposed to test anything
external
I agree with that. You can't test external things like an AJAX call anyway.
I'm unhappy with this, because it still doesn't tell me that
appendSoldier works like it's supposed to.
Actually, you CAN test the appendSoldier method. You need to refactor it in the following way:
Now you can unit test the functionality in the following way (I will use Jasmine because I'm familiar with it but the concepts can be mapped to Sinon):
external
I agree with that. You can't test external things like an AJAX call anyway.
I'm unhappy with this, because it still doesn't tell me that
appendSoldier works like it's supposed to.
Actually, you CAN test the appendSoldier method. You need to refactor it in the following way:
function appendSoldier() {
$.get("/ajax/soldier",
{
rank: rankField.val(),
name: nameField.val(),
}).done(
function(data) {
// Notice that I extracted these 2 lines of code into a separate method
// soldiers = extractSoldiersFromList(data);
// renderHTMLListFromData(soldiers);
appendSoldierSuccess(data);
}
);
clearInput();
};
// This is the extracted method
function appendSoldierSuccess(data) {
soldiers = extractSoldiersFromList(data);
renderHTMLListFromData(soldiers);
}Now you can unit test the functionality in the following way (I will use Jasmine because I'm familiar with it but the concepts can be mapped to Sinon):
it("appendSoldierSuccess()", function() {
var data = {};
var soldiers = {};
spyOn(window, 'extractSoldiersFromList').and.returnValue(soldiers);
spyOn(window, 'renderHTMLListFromData');
appendSoldierSuccess(data);
expect(window.extractSoldiersFromList).toHaveBeenCalledWith(data);
expect(window.renderHTMLListFromData).toHaveBeenCalledWith(soldiers);
});Code Snippets
function appendSoldier() {
$.get("/ajax/soldier",
{
rank: rankField.val(),
name: nameField.val(),
}).done(
function(data) {
// Notice that I extracted these 2 lines of code into a separate method
// soldiers = extractSoldiersFromList(data);
// renderHTMLListFromData(soldiers);
appendSoldierSuccess(data);
}
);
clearInput();
};
// This is the extracted method
function appendSoldierSuccess(data) {
soldiers = extractSoldiersFromList(data);
renderHTMLListFromData(soldiers);
}it("appendSoldierSuccess()", function() {
var data = {};
var soldiers = {};
spyOn(window, 'extractSoldiersFromList').and.returnValue(soldiers);
spyOn(window, 'renderHTMLListFromData');
appendSoldierSuccess(data);
expect(window.extractSoldiersFromList).toHaveBeenCalledWith(data);
expect(window.renderHTMLListFromData).toHaveBeenCalledWith(soldiers);
});Context
StackExchange Code Review Q#80695, answer score: 2
Revisions (0)
No revisions yet.