Google Tag Supervisor now helps you to add unit assessments on to your customized templates. That is helpful, because it means that you can management the code stability of your templates, particularly in the event you’ve determined to share these templates with the general public.
I not too long ago shared a normal information for a way template assessments work, however I wished to increase the subject slightly, and share with you two walkthroughs of customized template assessments: one for a variable template and one for a tag template.
There’s a video in the event you favor a extra visible strategy to stroll by these steps.
X
The Simmer Publication
Subscribe to the Simmer e-newsletter to get the most recent information and content material from Simo Ahava into your e-mail inbox!
Tip 107: The best way to construct tag and variable template assessments
Let’s get began. We’ll kick issues off with a variable template.
Variable template: String from array of objects
This walkthrough will make the most of my String from array of objects template.
The template could be very easy. It takes a GTM variable as an enter, the place the variable should return an array of objects (e.g. [{id: '123'},{id: '234'}]
).
Then, the consumer specifies which property from the objects they wish to concatenate the values of right into a string (e.g. id
), and eventually they will specify a delimiter apart from ,
. The top outcome can be one thing like '123,234'
if following the instance from the earlier paragraph.
Right here is the template code in all its simplicity:
const callInWindow = require('callInWindow');
const getType = require('getType');
const inputArray = knowledge.inputArray;
const keyToConcatenate = knowledge.keyToConcatenate;
const delimiter = knowledge.delimiter;
// If not an array, return undefined
if (getType(inputArray) !== 'array') {
return;
}
return inputArray
.map(obj => obj[keyToConcatenate])
.filter(obj => obj)
.be part of(delimiter);
After we deconstruct the variable code, there are 4 various things that may occur.
- The enter will not be an array, during which case the
getType()
test succeeds and the variable returnsundefined
. - The array doesn’t have any objects inside, during which case the
.map()
and.filter()
calls find yourself returning an empty array, which is then become an empty string by.be part of()
. - The array has objects however none of them has the desired key, during which case the
.map()
and.filter()
calls find yourself returning an empty array, which is then become an empty string by.be part of()
. - The array has at the very least one object with the given key, during which case the variable returns a string the place the important thing values are concatenated right into a string utilizing the delimiter.
In different phrases, we want 4 assessments.
Check 1: Return undefined if not array
If the variable will not be an array, the variable returns undefined
. To check this, we create a mock knowledge object, the place we go a non-array because the enter.
That is what the check appears to be like like:
// Name runCode to run the template's code.
const mockData = {
inputArray: 'notAnArray',
keyToConcatenate: 'identify',
delimiter: ','
};
const variableResult = runCode(mockData);
// Confirm that the variable returns a outcome.
assertThat(variableResult).isUndefined();
The mockData
object has the inputArray
set to a string, which isn’t an array. This object is handed to runCode()
, which executes the variable template as if the consumer had created the template with the non-array because the enter.
Lastly, the results of runCode()
is asserted by passing the check if the return worth of the variable is undefined
, through the use of the isUndefined()
assertion.
Check 2: Return empty string if no objects in array
Within the subsequent check, we have to create a state of affairs the place the consumer does present an array (so the getType()
test doesn’t catch it), however this array may have no objects inside. In that case, the variable would return an empty string.
// Name runCode to run the template's code.
const mockData = {
inputArray: [1, 2, 3],
keyToConcatenate: 'identify',
delimiter: ','
};
const anticipated = '';
const variableResult = runCode(mockData);
// Confirm that the variable returns a outcome.
assertThat(variableResult).isEqualTo(anticipated);
Right here we use the anticipated
fixed to make it simpler to handle and skim the code. Once more, the mock knowledge object is handed to runCode()
, and the variable result’s then asserted with the isEqualTo()
test, this time passing the check if the variable result’s precisely an empty string.
Check 3: Return empty string if objects in array should not have the important thing
To be honest, this may very well be bundled up with the earlier check, as a result of they check the identical traces of code and the top outcome is similar.
// Name runCode to run the template's code.
const mockData = {
inputArray: [{id: '123'},{category: 'shoes'}],
keyToConcatenate: 'identify',
delimiter: ','
};
const anticipated = '';
const variableResult = runCode(mockData);
// Confirm that the variable returns a outcome.
assertThat(variableResult).isEqualTo(anticipated);
As you may see from the mock object, we’re on the lookout for the important thing identify
within the objects of the array. Neither one of many objects within the inputArray
has that key.
As soon as this mock object is handed to runCode()
, the variable code is executed, and the variable finally ends up returning an empty string. That is asserted by checking the outcome in opposition to the anticipated
worth of ''
.
Check 4: Return concatenated string with delimiter for legitimate object keys
The final check is the one optimistic test we have to do.
// Name runCode to run the template's code.
const mockData = {
inputArray: [{name: 'firstName'},{name: 'secondName'}],
keyToConcatenate: 'identify',
delimiter: ','
};
const anticipated = 'firstName,secondName';
const variableResult = runCode(mockData);
// Confirm that the variable returns a outcome.
assertThat(variableResult).isEqualTo(anticipated);
This time, the mock knowledge array has two objects, the place each have the important thing identify
. Thus the anticipated
fixed is ready to 'firstName,secondName'
, as a result of that’s what we anticipate the variable to return.
That is asserted with the isEqualTo()
test once more.
Tag template: Consumer distributor
The second walkthrough will make the most of my consumer distributor template. I wrote about how the template works right here.
The tag template is sort of a bit extra advanced than the variable template above. For one, we’ve got branching choices, and we even have collective catch-alls that apply to all branches.
The consumer distributor helps you to select whether or not to isolate a single group or a number of teams of customers. If a random quantity matches the distribution you set for both choice, the consumer will be assigned to a gaggle, which is designated by writing a cookie within the browser.
In case the consumer is already in a gaggle, the code will not be executed, and in case the random quantity doesn’t match a distribution vary, the cookie will not be written.
So, the eventualities we have to check for are these:
- If cookie exists, i.e. the consumer has already been assigned to a gaggle, do nothing.
- If utilizing a single distribution, set cookie to true if the consumer is assigned to the group.
- If utilizing a single distribution, set cookie to false if the consumer will not be assigned to the group.
- Make certain the single distribution cookie is written with the appropriate choices.
- If utilizing a multi distribution, set cookie to the first group when the randomizer is throughout the vary.
- If utilizing a multi distribution, set cookie to the second group when the randomizer is throughout the vary.
- If utilizing a multi distribution, don’t set cookie if the randomizer is exterior all group ranges.
- Make certain the multi distribution cookie is written with the appropriate choices.
These eight assessments ought to get 100% protection for the tag template. And for reference, right here is the complete tag template code:
const setCookie = require('setCookie');
const readCookie = require('getCookieValues');
const generateRandom = require('generateRandom');
const makeInteger = require('makeInteger');
const log = require('logToConsole');
// Proportion accumulation for multi
let acc = 0;
// Randomizer
const rand = generateRandom(1, 100);
// Consumer knowledge
const cookieName = knowledge.cookieName;
const cookieDomain = knowledge.cookieDomain;
const cookieMaxAge = knowledge.cookieExpires * 24 * 60 * 60;
const single = knowledge.singlePercent;
const multi = knowledge.multiPercent;
// Solely run if cookie will not be set
if (readCookie(cookieName).size === 0) {
// If single distribution
if (single) {
setCookie(
cookieName,
(rand <= single ? 'true' : 'false'),
{
area: cookieDomain,
'max-age': cookieMaxAge
}
);
}
// If multi distribution
if (multi) {
multi.some(obj => {
acc += makeInteger(obj.likelihood);
if (rand <= acc) {
setCookie(
cookieName,
obj.worth,
{
area: cookieDomain,
'max-age': cookieMaxAge
}
);
return true;
}
});
}
}
knowledge.gtmOnSuccess();
Let’s begin with a check setup.
Setup: initialize the mock objects
This time round, we’ll be working a number of assessments to test completely different facets of the identical mock knowledge setups. Thus, as a substitute of at all times initializing the mock objects for every check with (nearly) equivalent values, we will as a substitute create the mock objects within the Setup of the check, after which they are going to be within the scope of all check instances.
That is what the setup code appears to be like like:
const mockSingleData = {
cookieName: 'userDistributor',
cookieDomain: 'auto',
cookieExpires: 365,
singlePercent: 50
};
const mockMultiData = {
cookieName: 'userDistributor',
cookieDomain: 'auto',
cookieExpires: 365,
multiPercent: [{
value: 'A',
probability: 33
},{
value: 'B',
probability: 33
}]
};
The single distribution mock units the cookie userDistributor
to 'true'
if the randomizer generates a quantity between 1 and 50. Numbers between 51 and 100 set the cookie worth to 'false'
.
The multi distribution mock units the cookie userDistributor
to 'A'
if the randomizer generates a quantity between 1 and 33, to 'B'
for values between 34 and 66, and doesn’t set the cookie for values between 67 and 100.
In each instances, the cookie is written with the 'auto'
area, and the expiration is ready to 12 months.
Check 1: Do nothing if cookie exists
The primary state of affairs we’ll check is what occurs if the cookie named userDistributor
has already been set. On this case, the template shouldn’t actually do something – at the very least, it shouldn’t reset the cookie with a brand new worth.
mock('getCookieValues', [true]);
// Name runCode to run the template's code.
runCode(mockSingleData);
assertApi('generateRandom').wasCalled();
assertApi('getCookieValues').wasCalled();
assertApi('makeInteger').wasNotCalled();
assertApi('setCookie').wasNotCalled();
// Confirm that the tag completed efficiently.
assertApi('gtmOnSuccess').wasCalled();
We use the mock()
API right here, which lets us pretend the results of a customized template API. On this case, we’re instructing the template to return the worth [true]
every time the getCookieValues
API is invoked. The worth throughout the array is inconsequential – all it has to do is return an array with a optimistic size for the template to consider that the cookie has been set.
Subsequent, the single mock knowledge object is handed to runCode()
.
After that, we merely test if the code has run by checking which customized template APIs have been referred to as. We will see from the code that generateRandom()
and getCookieValues()
are referred to as earlier than any checks are completed, however makeInteger()
and setCookie()
are solely referred to as if the cookie has not been set but.
Thus, we will use the wasCalled()
and wasNotCalled()
APIs to test which department of the template code was executed.
Lastly, we be sure that gtmOnSuccess()
was referred to as, since in any other case the tag would stall.
Check 2: Set single cookie to true when in distribution
The following case we’ll check is that the cookie is ready to worth 'true'
when the randomizer generates a price that’s throughout the vary of the one distribution group likelihood.
mock('getCookieValues', []);
mock('generateRandom', 50);
mock('setCookie', (identify, worth, choices) => {
if (worth !== 'true') {
fail('setCookie not referred to as with "true"');
}
});
// Name runCode to run the template's code.
runCode(mockSingleData);
assertApi('generateRandom').wasCalled();
assertApi('getCookieValues').wasCalled();
assertApi('makeInteger').wasNotCalled();
assertApi('setCookie').wasCalled();
// Confirm that the tag completed efficiently.
assertApi('gtmOnSuccess').wasCalled();
Try the mock()
calls. The primary one is much like the one within the earlier check, besides this time we’ve got it return an empty array to suggest the cookie has not been set.
Then, we mock the generateRandom()
API to have it return the worth 50
, simply to check the higher sure of the distribution vary of 1...50
as set within the mock object throughout the Setup of the check.
The third mock()
is fascinating! We’re mocking setCookie()
as a result of a) we don’t need it to really set a cookie, and b) we will use this patch to test what setCookie()
was referred to as with. In case setCookie()
was not referred to as with the worth 'true'
, we fail the check.
You need to use the mock()
along with fail()
to test what APIs have been referred to as with – it’s a pleasant workaround whereas we watch for wasCalledWith()
for the assertApi()
API.
Then, we run the code with the mock knowledge object once more.
The checklist of APIs we anticipate to be referred to as is nearly the identical as within the earlier check, with the exception that now we anticipate setCookie()
to be referred to as, for the reason that userDistributor
cookie doesn’t exist on this state of affairs.
This check succeeds if the setCookie()
API is named with the anticipated worth of 'true'
for the cookie.
Check 3: Set single cookie to false when not in distribution
This check is mainly a mirror of the earlier one, besides we set a unique return worth for the generateRandom()
mock to make sure the cookie is ready with the worth 'false'
. We’re utilizing the decrease sure of the vary (51
) simply to check the extremes of the algorithm.
mock('getCookieValues', []);
mock('generateRandom', 51);
mock('setCookie', (identify, worth, choices) => {
if (worth !== 'false') {
fail('setCookie not referred to as with "false"');
}
});
// Name runCode to run the template's code.
runCode(mockSingleData);
assertApi('generateRandom').wasCalled();
assertApi('getCookieValues').wasCalled();
assertApi('makeInteger').wasNotCalled();
assertApi('setCookie').wasCalled();
// Confirm that the tag completed efficiently.
assertApi('gtmOnSuccess').wasCalled();
Aside from these two adjustments, the check code is equivalent to the earlier one.
Check 4: Set single cookie with appropriate choices
This check is a straightforward test to ensure that the cookie settings (identify, worth, area, and expiration) correspond with these set within the template configuration. It’s a strong check to write down, as a result of typically the little configuration errors that don’t actually break the code are those that slip by the cracks when creating incremental updates.
mock('getCookieValues', []);
mock('generateRandom', 99);
mock('setCookie', (identify, worth, choices) => {
if (identify !== mockSingleData.cookieName) fail('setCookie referred to as with incorrect cookie identify');
if (worth !== 'false') fail('setCookie not referred to as with "false"');
if (choices.area !== mockSingleData.cookieDomain) fail('setCookie referred to as with incorrect cookie area');
if (choices['max-age'] !== mockSingleData.cookieExpires * 24 * 60 * 60) fail('setCookie referred to as with incorrect max-age');
});
// Name runCode to run the template's code.
runCode(mockSingleData);
assertApi('generateRandom').wasCalled();
assertApi('getCookieValues').wasCalled();
assertApi('makeInteger').wasNotCalled();
assertApi('setCookie').wasCalled();
// Confirm that the tag completed efficiently.
assertApi('gtmOnSuccess').wasCalled();
The magic occurs within the setCookie()
mock. Mainly, we fail the check if the setCookie()
API is named with values that don’t correspond to these we set within the mock knowledge object.
Check 5: Set multi cookie to A when equal to 33
After we begin testing the multi distribution choices, we’re mainly checking if the randomized quantity falls into the distributions established within the tag settings.
We’ll first check the only state of affairs: the randomizer picks the primary group the consumer outlined. We use this by setting it to the higher sure of the primary group.
mock('getCookieValues', []);
mock('generateRandom', 33);
mock('setCookie', (identify, worth, choices) => {
if (worth !== 'A') {
fail('setCookie not referred to as with "A"');
}
});
// Name runCode to run the template's code.
runCode(mockMultiData);
assertApi('generateRandom').wasCalled();
assertApi('getCookieValues').wasCalled();
assertApi('makeInteger').wasCalled();
assertApi('setCookie').wasCalled();
// Confirm that the tag completed efficiently.
assertApi('gtmOnSuccess').wasCalled();
The setup is similar to the earlier single distribution assessments, with the distinction that the cookie worth is now established within the mock knowledge object, and we anticipate the makeInteger()
API to be referred to as for the template check to succeed.
Check 6: Set multi cookie to B when equal to 34
This check is virtually equivalent to the earlier one, with the exception that we set the generateRandom()
API to return a price that sits throughout the decrease sure of the second group of the mock object, and we modify the setCookie()
API to test for the corresponding worth ('B'
) as a substitute.
mock('getCookieValues', []);
mock('generateRandom', 34);
mock('setCookie', (identify, worth, choices) => {
if (worth !== 'B') {
fail('setCookie not referred to as with "B"');
}
});
// Name runCode to run the template's code.
runCode(mockMultiData);
assertApi('generateRandom').wasCalled();
assertApi('getCookieValues').wasCalled();
assertApi('makeInteger').wasCalled();
assertApi('setCookie').wasCalled();
// Confirm that the tag completed efficiently.
assertApi('gtmOnSuccess').wasCalled();
Check 7: Don’t set multi cookie if not inside distributions
In case the randomizer doesn’t fall into any of the likelihood ranges established within the multi distribution settings, the cookie shouldn’t be set.
The best strategy to test that is by ensuring the setCookie()
API was not referred to as. You are able to do this with assertApi('setCookie').wasNotCalled()
, however it’s also possible to mock the setCookie()
API to fail if referred to as. I’ve opted to do each only for giggles.
mock('getCookieValues', []);
mock('generateRandom', 67);
mock('setCookie', (identify, worth, choices) => {
fail('setCookie shouldn't have been referred to as');
});
// Name runCode to run the template's code.
runCode(mockMultiData);
assertApi('generateRandom').wasCalled();
assertApi('getCookieValues').wasCalled();
assertApi('makeInteger').wasCalled();
assertApi('setCookie').wasNotCalled();
// Confirm that the tag completed efficiently.
assertApi('gtmOnSuccess').wasCalled();
Right here, we generate a random quantity that’s above 66 to verify the quantity doesn’t belong to any group. Then, we mock setCookie()
to fail the check if ever referred to as (no matter what values it was referred to as with), and we additionally use assertApi()
to go the check provided that the API was not referred to as.
Check 8: Set multi cookie with appropriate choices
That is much like Check 4, since we’re simply checking that the cookie is ready with choices that correspond with these configured within the template settings (or within the mock knowledge object as on this case). The check will fail if there are any variations.
mock('getCookieValues', []);
mock('generateRandom', 20);
mock('setCookie', (identify, worth, choices) => {
if (identify !== mockMultiData.cookieName) fail('setCookie referred to as with incorrect cookie identify');
if (worth !== mockMultiData.multiPercent[0].worth) fail('setCookie referred to as with incorrect cookie worth');
if (choices.area !== mockMultiData.cookieDomain) fail('setCookie referred to as with incorrect cookie area');
if (choices['max-age'] !== mockMultiData.cookieExpires * 24 * 60 * 60) fail('setCookie referred to as with incorrect max-age');
});
// Name runCode to run the template's code.
runCode(mockMultiData);
assertApi('generateRandom').wasCalled();
assertApi('getCookieValues').wasCalled();
assertApi('makeInteger').wasCalled();
assertApi('setCookie').wasCalled();
// Confirm that the tag completed efficiently.
assertApi('gtmOnSuccess').wasCalled();
Abstract
On this article, I confirmed you two walkthroughs for the best way to write assessments in your variable and tag templates.
A great way to get began is to determine all of the branches of your code, after which write assessments that test for all of the permutations.
To cut back the variety of assessments you want, be sure you make the most of the area configurations (notably the validation guidelines) effectively. By blocking invalid enter earlier than the tag or variable may even be saved you’re lowering the variety of validation checks you might want to write (and check for) within the code.
Hopefully we’ll get some check protection particulars into the UI, to be able to plan your assessments to cowl all doable branches and options of the template code.
Lastly, keep in mind to replace the assessments whenever you replace the template itself. Writing and updating assessments ought to grow to be half and parcel of your high quality assurance course of when writing templates. You owe it to the customers who find yourself putting in your template into their Google Tag Supervisor containers!