{"id":92788,"date":"2025-02-17T02:57:45","date_gmt":"2025-02-17T02:57:45","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/gtmtips-walkthrough-of-custom-template-tests\/"},"modified":"2025-02-17T02:57:45","modified_gmt":"2025-02-17T02:57:45","slug":"gtmtips-walkthrough-of-custom-template-tests","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=92788","title":{"rendered":"#GTMTips: Walkthrough Of Custom Template Tests"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p><a href=\"https:\/\/tagmanager.google.com\/\">Google Tag Manager<\/a> now lets you add <strong>unit tests<\/strong> directly to your <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/\">custom templates<\/a>. This is useful, since it allows you to control the code stability of your templates, especially if you\u2019ve decided to <a href=\"https:\/\/www.simoahava.com\/analytics\/introducing-tag-manager-template-gallery\/\">share those templates with the public<\/a>.<\/p>\n<p>I recently shared <a href=\"https:\/\/www.simoahava.com\/analytics\/writing-tests-for-custom-templates-google-tag-manager\/\">a general guide<\/a> for how template tests work, but I wanted to expand the topic a little, and share with you two walkthroughs of custom template tests: one for a <strong>variable template<\/strong> and one for a <strong>tag template<\/strong>.<\/p>\n<p>There\u2019s a <a href=\"https:\/\/youtu.be\/UZ8vyFbYJcY\">video<\/a> if you prefer a more visual way to walk through these steps.<\/p>\n<p>\n  <iframe src=\"https:\/\/www.youtube-nocookie.com\/embed\/UZ8vyFbYJcY\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" allowfullscreen=\"\" title=\"YouTube Video\"><\/iframe>\n<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"tip-107-how-to-build-tag-and-variable-template-tests\">Tip 107: How to build tag and variable template tests<\/h2>\n<div style=\"aspect-ratio: 960 \/ 540;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/11\/walkthrough-custom-template-tests.jpg\" title=\"Walkthrough of custom template tests\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"540\" width=\"960\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/11\/walkthrough-custom-template-tests.jpg#ZgotmplZ\" alt=\"Walkthrough of custom template tests\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Let\u2019s get started. We\u2019ll kick things off with a <strong>variable template<\/strong>.<\/p>\n<h3 id=\"variable-template-string-from-array-of-objects\">Variable template: String from array of objects<\/h3>\n<p>This walkthrough will utilize my <a href=\"https:\/\/tagmanager.google.com\/gallery\/#\/owners\/gtm-templates-simo-ahava\/templates\/string-from-array-of-objects\">String from array of objects<\/a> template.<\/p>\n<div style=\"aspect-ratio: 926 \/ 572;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/11\/string-from-array-objects.jpg\" title=\"String from array of templates\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"572\" width=\"926\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/11\/string-from-array-objects.jpg#ZgotmplZ\" alt=\"String from array of templates\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The template is very simple. It takes a GTM variable as an input, where the variable must return an <strong>array of objects<\/strong> (e.g. <code>[{id: '123'},{id: '234'}]<\/code>).<\/p>\n<p>Then, the user specifies which property from the objects they want to concatenate the values of into a string (e.g. <code>id<\/code>), and finally they can specify a <strong>delimiter<\/strong> other than <code>,<\/code>. The end result would be something like <code>'123,234'<\/code> if following the example from the previous paragraph.<\/p>\n<p>Here is the template code in all its simplicity:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">const<\/span> callInWindow = require(<span style=\"color:#a50\">'callInWindow'<\/span>);\n<span style=\"color:#00a\">const<\/span> getType = require(<span style=\"color:#a50\">'getType'<\/span>);\n\n<span style=\"color:#00a\">const<\/span> inputArray = data.inputArray;\n<span style=\"color:#00a\">const<\/span> keyToConcatenate = data.keyToConcatenate;\n<span style=\"color:#00a\">const<\/span> delimiter = data.delimiter;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ If not an array, return undefined\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">if<\/span> (getType(inputArray) !== <span style=\"color:#a50\">'array'<\/span>) {\n  <span style=\"color:#00a\">return<\/span>;\n}\n\n<span style=\"color:#00a\">return<\/span> inputArray\n  .map(obj =&gt; obj[keyToConcatenate])\n  .filter(obj =&gt; obj)\n  .join(delimiter);\n<\/code><\/pre>\n<\/div>\n<p>When we deconstruct the variable code, there are <strong>four<\/strong> different things that can happen.<\/p>\n<ol>\n<li><strong>The input is not an array<\/strong>, in which case the <code>getType()<\/code> check succeeds and the variable returns <code>undefined<\/code>.<\/li>\n<li><strong>The array does not have any objects within<\/strong>, in which case the <code>.map()<\/code> and <code>.filter()<\/code> calls end up returning an empty array, which is then turned into an <strong>empty string<\/strong> by <code>.join()<\/code>.<\/li>\n<li><strong>The array has objects but none of them has the specified key<\/strong>, in which case the <code>.map()<\/code> and <code>.filter()<\/code> calls end up returning an empty array, which is then turned into an <strong>empty string<\/strong> by <code>.join()<\/code>.<\/li>\n<li><strong>The array has at least one object with the given key<\/strong>, in which case the variable returns a string where the key values are concatenated into a string using the delimiter.<\/li>\n<\/ol>\n<p>In other words, we need <strong>four tests<\/strong>.<\/p>\n<h4 id=\"test-1-return-undefined-if-not-array\">Test 1: Return undefined if not array<\/h4>\n<p>If the variable is not an array, the variable returns <code>undefined<\/code>. To test this, we create a <strong>mock<\/strong> data object, where we pass a non-array as the input.<\/p>\n<p>This is what the test looks like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> mockData = {\n  inputArray: <span style=\"color:#a50\">'notAnArray'<\/span>,\n  keyToConcatenate: <span style=\"color:#a50\">'name'<\/span>,\n  delimiter: <span style=\"color:#a50\">','<\/span>\n};\n\n<span style=\"color:#00a\">const<\/span> variableResult = runCode(mockData);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the variable returns a result.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertThat(variableResult).isUndefined();\n<\/code><\/pre>\n<\/div>\n<p>The <code>mockData<\/code> object has the <code>inputArray<\/code> set to a <strong>string<\/strong>, which is not an array. This object is passed to <code>runCode()<\/code>, which executes the variable template <em>as if<\/em> the user had created the template with the non-array as the input.<\/p>\n<p>Finally, the result of <code>runCode()<\/code> is asserted by passing the test if the return value of the variable is <code>undefined<\/code>, by using the <code>isUndefined()<\/code> assertion.<\/p>\n<h4 id=\"test-2-return-empty-string-if-no-objects-in-array\">Test 2: Return empty string if no objects in array<\/h4>\n<p>In the next test, we need to create a scenario where the user does provide an array (so the <code>getType()<\/code> check doesn\u2019t catch it), but this array will have no objects within. In that case, the variable would return an empty string.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> mockData = {\n  inputArray: [<span style=\"color:#099\">1<\/span>, <span style=\"color:#099\">2<\/span>, <span style=\"color:#099\">3<\/span>],\n  keyToConcatenate: <span style=\"color:#a50\">'name'<\/span>,\n  delimiter: <span style=\"color:#a50\">','<\/span>\n};\n\n<span style=\"color:#00a\">const<\/span> expected = <span style=\"color:#a50\">''<\/span>;\n\n<span style=\"color:#00a\">const<\/span> variableResult = runCode(mockData);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the variable returns a result.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertThat(variableResult).isEqualTo(expected);\n<\/code><\/pre>\n<\/div>\n<p>Here we use the <code>expected<\/code> constant to make it easier to manage and read the code. Again, the mock data object is passed to <code>runCode()<\/code>, and the variable result is then asserted with the <code>isEqualTo()<\/code> check, this time passing the test if the variable result is exactly an empty string.<\/p>\n<h4 id=\"test-3-return-empty-string-if-objects-in-array-do-not-have-the-key\">Test 3: Return empty string if objects in array do not have the key<\/h4>\n<p>To be fair, this could be bundled up with the previous test, because they test the same lines of code and the end result is the same.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> mockData = {\n  inputArray: [{id: <span style=\"color:#a50\">'123'<\/span>},{category: <span style=\"color:#a50\">'shoes'<\/span>}],\n  keyToConcatenate: <span style=\"color:#a50\">'name'<\/span>,\n  delimiter: <span style=\"color:#a50\">','<\/span>\n};\n\n<span style=\"color:#00a\">const<\/span> expected = <span style=\"color:#a50\">''<\/span>;\n\n<span style=\"color:#00a\">const<\/span> variableResult = runCode(mockData);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the variable returns a result.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertThat(variableResult).isEqualTo(expected);\n<\/code><\/pre>\n<\/div>\n<p>As you can see from the mock object, we are looking for the key <code>name<\/code> in the objects of the array. Neither one of the objects in the <code>inputArray<\/code> has that key.<\/p>\n<p>Once this mock object is passed to <code>runCode()<\/code>, the variable code is executed, and the variable ends up returning an empty string. This is asserted by checking the result against the <code>expected<\/code> value of <code>''<\/code>.<\/p>\n<h4 id=\"test-4-return-concatenated-string-with-delimiter-for-valid-object-keys\">Test 4: Return concatenated string with delimiter for valid object keys<\/h4>\n<p>The last test is the only <strong>positive<\/strong> check we need to do.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> mockData = {\n  inputArray: [{name: <span style=\"color:#a50\">'firstName'<\/span>},{name: <span style=\"color:#a50\">'secondName'<\/span>}],\n  keyToConcatenate: <span style=\"color:#a50\">'name'<\/span>,\n  delimiter: <span style=\"color:#a50\">','<\/span>\n};\n\n<span style=\"color:#00a\">const<\/span> expected = <span style=\"color:#a50\">'firstName,secondName'<\/span>;\n\n<span style=\"color:#00a\">const<\/span> variableResult = runCode(mockData);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the variable returns a result.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertThat(variableResult).isEqualTo(expected);\n<\/code><\/pre>\n<\/div>\n<p>This time, the mock data array has two objects, where both have the key <code>name<\/code>. Thus the <code>expected<\/code> constant is set to <code>'firstName,secondName'<\/code>, because that is what we expect the variable to return.<\/p>\n<p>This is asserted with the <code>isEqualTo()<\/code> check again.<\/p>\n<h3 id=\"tag-template-user-distributor\">Tag template: User distributor<\/h3>\n<p>The second walkthrough will utilize my <a href=\"https:\/\/tagmanager.google.com\/gallery\/#\/owners\/gtm-templates-simo-ahava\/templates\/user-distributor\">user distributor<\/a> template. I wrote about how the template works <a href=\"https:\/\/www.simoahava.com\/analytics\/user-distributor-tag-template\/\">here<\/a>.<\/p>\n<div style=\"aspect-ratio: 924 \/ 786;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/11\/user-distributor-template.jpg\" title=\"User distributor template\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"786\" width=\"924\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/11\/user-distributor-template.jpg#ZgotmplZ\" alt=\"User distributor template\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The tag template is quite a bit more complex than the variable template above. For one, we have <strong>branching decisions<\/strong>, and we also have collective <strong>catch-alls<\/strong> that apply to all branches.<\/p>\n<p>The <strong>user distributor<\/strong> lets you choose whether to isolate a <strong>single<\/strong> group or <strong>multiple<\/strong> groups of users. If a random number matches the distribution you set for either option, the user can be assigned to a group, which is designated by writing a cookie in the browser.<\/p>\n<p>In case the user is already in a group, the code is not executed, and in case the random number does not match a distribution range, the cookie is not written.<\/p>\n<p>So, the scenarios we need to test for are these:<\/p>\n<ol>\n<li>If <strong>cookie exists<\/strong>, i.e. the user has already been assigned to a group, <strong>do nothing<\/strong>.<\/li>\n<li>If using a <strong>single<\/strong> distribution, set cookie to <strong>true<\/strong> if the user is assigned to the group.<\/li>\n<li>If using a <strong>single<\/strong> distribution, set cookie to <strong>false<\/strong> if the user is not assigned to the group.<\/li>\n<li>Make sure the <strong>single<\/strong> distribution cookie is written with the <strong>correct options<\/strong>.<\/li>\n<li>If using a <strong>multi<\/strong> distribution, set cookie to the <strong>first group<\/strong> when the randomizer is within the range.<\/li>\n<li>If using a <strong>multi<\/strong> distribution, set cookie to the <strong>second group<\/strong> when the randomizer is within the range.<\/li>\n<li>If using a <strong>multi<\/strong> distribution, <strong>do not set cookie<\/strong> if the randomizer is outside all group ranges.<\/li>\n<li>Make sure the <strong>multi<\/strong> distribution cookie is written with the <strong>correct options<\/strong>.<\/li>\n<\/ol>\n<p>These eight tests should get 100% coverage for the tag template. And for reference, here is the full tag template code:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">const<\/span> setCookie = require(<span style=\"color:#a50\">'setCookie'<\/span>);\n<span style=\"color:#00a\">const<\/span> readCookie = require(<span style=\"color:#a50\">'getCookieValues'<\/span>);\n<span style=\"color:#00a\">const<\/span> generateRandom = require(<span style=\"color:#a50\">'generateRandom'<\/span>);\n<span style=\"color:#00a\">const<\/span> makeInteger = require(<span style=\"color:#a50\">'makeInteger'<\/span>);\n<span style=\"color:#00a\">const<\/span> log = require(<span style=\"color:#a50\">'logToConsole'<\/span>);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Percentage accumulation for multi\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">let<\/span> acc = <span style=\"color:#099\">0<\/span>;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Randomizer\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> rand = generateRandom(<span style=\"color:#099\">1<\/span>, <span style=\"color:#099\">100<\/span>);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ User data\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> cookieName = data.cookieName;\n<span style=\"color:#00a\">const<\/span> cookieDomain = data.cookieDomain;\n<span style=\"color:#00a\">const<\/span> cookieMaxAge = data.cookieExpires * <span style=\"color:#099\">24<\/span> * <span style=\"color:#099\">60<\/span> * <span style=\"color:#099\">60<\/span>;\n<span style=\"color:#00a\">const<\/span> single = data.singlePercent;\n<span style=\"color:#00a\">const<\/span> multi = data.multiPercent;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Only run if cookie is not set\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">if<\/span> (readCookie(cookieName).length === <span style=\"color:#099\">0<\/span>) {\n\n  <span style=\"color:#aaa;font-style:italic\">\/\/ If single distribution\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#00a\">if<\/span> (single) {\n    setCookie(\n      cookieName,\n      (rand 'true' : <span style=\"color:#a50\">'false'<\/span>),\n      {\n        domain: cookieDomain,\n        <span style=\"color:#a50\">'max-age'<\/span>: cookieMaxAge\n      }\n    );\n  }\n\n  <span style=\"color:#aaa;font-style:italic\">\/\/ If multi distribution\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#00a\">if<\/span> (multi) {\n    multi.some(obj =&gt; {\n      acc += makeInteger(obj.probability);\n      <span style=\"color:#00a\">if<\/span> (rand 'max-age': cookieMaxAge\n          }\n        );\n        <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">true<\/span>;\n      }\n    });\n  }\n\n}\n\ndata.gtmOnSuccess();\n<\/code><\/pre>\n<\/div>\n<p>Let\u2019s start with a <strong>test setup<\/strong>.<\/p>\n<h4 id=\"setup-initialize-the-mock-objects\">Setup: initialize the mock objects<\/h4>\n<p>This time around, we\u2019ll be running multiple tests to check different aspects of the same mock data setups. Thus, instead of always initializing the mock objects for each test with (almost) identical values, we can instead create the mock objects in the <strong>Setup<\/strong> of the test, after which they will be in the scope of all test cases.<\/p>\n<div style=\"aspect-ratio: 776 \/ 314;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/11\/distributor-test-setup.jpg\" title=\"test setup\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"314\" width=\"776\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/11\/distributor-test-setup.jpg#ZgotmplZ\" alt=\"test setup\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This is what the setup code looks like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">const<\/span> mockSingleData = {\n  cookieName: <span style=\"color:#a50\">'userDistributor'<\/span>,\n  cookieDomain: <span style=\"color:#a50\">'auto'<\/span>,\n  cookieExpires: <span style=\"color:#099\">365<\/span>,\n  singlePercent: <span style=\"color:#099\">50<\/span>\n};\n\n<span style=\"color:#00a\">const<\/span> mockMultiData = {\n  cookieName: <span style=\"color:#a50\">'userDistributor'<\/span>,\n  cookieDomain: <span style=\"color:#a50\">'auto'<\/span>,\n  cookieExpires: <span style=\"color:#099\">365<\/span>,\n  multiPercent: [{\n    value: <span style=\"color:#a50\">'A'<\/span>,\n    probability: <span style=\"color:#099\">33<\/span>\n  },{\n    value: <span style=\"color:#a50\">'B'<\/span>,\n    probability: <span style=\"color:#099\">33<\/span>\n  }]\n};\n<\/code><\/pre>\n<\/div>\n<p>The <strong>single<\/strong> distribution mock sets the cookie <code>userDistributor<\/code> to <code>'true'<\/code> if the randomizer generates a number between <strong>1<\/strong> and <strong>50<\/strong>. Numbers between <strong>51<\/strong> and <strong>100<\/strong> set the cookie value to <code>'false'<\/code>.<\/p>\n<p>The <strong>multi<\/strong> distribution mock sets the cookie <code>userDistributor<\/code> to <code>'A'<\/code> if the randomizer generates a number between <strong>1<\/strong> and <strong>33<\/strong>, to <code>'B'<\/code> for values between <strong>34<\/strong> and <strong>66<\/strong>, and does not set the cookie for values between <strong>67<\/strong> and <strong>100<\/strong>.<\/p>\n<p>In both cases, the cookie is written with the <code>'auto'<\/code> domain, and the expiration is set to <strong>365 days<\/strong>.<\/p>\n<h4 id=\"test-1-do-nothing-if-cookie-exists\">Test 1: Do nothing if cookie exists<\/h4>\n<p>The first scenario we\u2019ll test is what happens if the cookie named <code>userDistributor<\/code> has already been set. In this case, the template shouldn\u2019t really do anything &#8211; at least, it shouldn\u2019t reset the cookie with a new value.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">mock(<span style=\"color:#a50\">'getCookieValues'<\/span>, [<span style=\"color:#00a\">true<\/span>]);\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>runCode(mockSingleData);\n\nassertApi(<span style=\"color:#a50\">'generateRandom'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'getCookieValues'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'makeInteger'<\/span>).wasNotCalled();\nassertApi(<span style=\"color:#a50\">'setCookie'<\/span>).wasNotCalled();\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the tag finished successfully.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertApi(<span style=\"color:#a50\">'gtmOnSuccess'<\/span>).wasCalled();\n<\/code><\/pre>\n<\/div>\n<p>We use the <code>mock()<\/code> API here, which lets us fake the result of a custom template API. In this case, we\u2019re instructing the template to return the value <code>[true]<\/code> whenever the <code>getCookieValues<\/code> API is invoked. The value within the array is inconsequential &#8211; all it has to do is return an array with a positive length for the template to believe that the cookie has been set.<\/p>\n<p>Next, the <strong>single<\/strong> mock data object is passed to <code>runCode()<\/code>.<\/p>\n<p>After that, we simply check if the code has run by checking which custom template APIs have been called. We can see from the code that <code>generateRandom()<\/code> and <code>getCookieValues()<\/code> are called before any checks are done, but <code>makeInteger()<\/code> and <code>setCookie()<\/code> are only called if the cookie has not been set yet.<\/p>\n<p>Thus, we can use the <code>wasCalled()<\/code> and <code>wasNotCalled()<\/code> APIs to check which branch of the template code was executed.<\/p>\n<p>Finally, we ensure that <code>gtmOnSuccess()<\/code> was called, since otherwise the tag would stall.<\/p>\n<h4 id=\"test-2-set-single-cookie-to-true-when-in-distribution\">Test 2: Set single cookie to true when in distribution<\/h4>\n<p>The next case we\u2019ll test is that the cookie is set to value <code>'true'<\/code> when the randomizer generates a value that is within the range of the single distribution group probability.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">mock(<span style=\"color:#a50\">'getCookieValues'<\/span>, []);\nmock(<span style=\"color:#a50\">'generateRandom'<\/span>, <span style=\"color:#099\">50<\/span>);\nmock(<span style=\"color:#a50\">'setCookie'<\/span>, (name, value, options) =&gt; {\n  <span style=\"color:#00a\">if<\/span> (value !== <span style=\"color:#a50\">'true'<\/span>) {\n    fail(<span style=\"color:#a50\">'setCookie not called with \"true\"'<\/span>);\n  }\n});\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>runCode(mockSingleData);\n\nassertApi(<span style=\"color:#a50\">'generateRandom'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'getCookieValues'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'makeInteger'<\/span>).wasNotCalled();\nassertApi(<span style=\"color:#a50\">'setCookie'<\/span>).wasCalled();\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the tag finished successfully.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertApi(<span style=\"color:#a50\">'gtmOnSuccess'<\/span>).wasCalled();\n<\/code><\/pre>\n<\/div>\n<p>Check out the <code>mock()<\/code> calls. The first one is similar to the one in the previous test, except this time we have it return an empty array to signify the cookie has <strong>not<\/strong> been set.<\/p>\n<p>Then, we mock the <code>generateRandom()<\/code> API to have it return the value <code>50<\/code>, just to test the upper bound of the distribution range of <code>1...50<\/code> as set in the mock object within the <strong>Setup<\/strong> of the test.<\/p>\n<p>The third <code>mock()<\/code> is interesting! We are mocking <code>setCookie()<\/code> because a) we don\u2019t want it to actually set a cookie, and b) we can use this patch to check what <code>setCookie()<\/code> was called <em>with<\/em>. In case <code>setCookie()<\/code> was not called with the value <code>'true'<\/code>, we <strong>fail<\/strong> the test.<\/p>\n<p>You can use the <code>mock()<\/code> together with <code>fail()<\/code> to check what APIs have been called with &#8211; it\u2019s a nice workaround while we wait for <code>wasCalledWith()<\/code> for the <code>assertApi()<\/code> API.<\/p>\n<p>Then, we run the code with the mock data object again.<\/p>\n<p>The list of APIs we expect to be called is almost the same as in the previous test, with the exception that now we <strong>expect <code>setCookie()<\/code><\/strong> to be called, since the <code>userDistributor<\/code> cookie does not exist in this scenario.<\/p>\n<p>This test succeeds if the <code>setCookie()<\/code> API is called with the expected value of <code>'true'<\/code> for the cookie.<\/p>\n<h4 id=\"test-3-set-single-cookie-to-false-when-not-in-distribution\">Test 3: Set single cookie to false when not in distribution<\/h4>\n<p>This test is basically a mirror of the previous one, except we set a different return value for the <code>generateRandom()<\/code> mock to ensure the cookie is set with the value <code>'false'<\/code>. We\u2019re using the lower bound of the range (<code>51<\/code>) just to test the extremes of the algorithm.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">mock(<span style=\"color:#a50\">'getCookieValues'<\/span>, []);\nmock(<span style=\"color:#a50\">'generateRandom'<\/span>, <span style=\"color:#099\">51<\/span>);\nmock(<span style=\"color:#a50\">'setCookie'<\/span>, (name, value, options) =&gt; {\n  <span style=\"color:#00a\">if<\/span> (value !== <span style=\"color:#a50\">'false'<\/span>) {\n    fail(<span style=\"color:#a50\">'setCookie not called with \"false\"'<\/span>);\n  }\n});\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>runCode(mockSingleData);\n\nassertApi(<span style=\"color:#a50\">'generateRandom'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'getCookieValues'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'makeInteger'<\/span>).wasNotCalled();\nassertApi(<span style=\"color:#a50\">'setCookie'<\/span>).wasCalled();\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the tag finished successfully.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertApi(<span style=\"color:#a50\">'gtmOnSuccess'<\/span>).wasCalled();\n<\/code><\/pre>\n<\/div>\n<p>Other than those two changes, the test code is identical to the previous one.<\/p>\n<h4 id=\"test-4-set-single-cookie-with-correct-options\">Test 4: Set single cookie with correct options<\/h4>\n<p>This test is a simple check to make sure that the cookie settings (name, value, domain, and expiration) correspond with those set in the template configuration. It\u2019s a solid test to write, because often the little configuration mistakes that don\u2019t really break the code are the ones that slip through the cracks when creating incremental updates.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">mock(<span style=\"color:#a50\">'getCookieValues'<\/span>, []);\nmock(<span style=\"color:#a50\">'generateRandom'<\/span>, <span style=\"color:#099\">99<\/span>);\nmock(<span style=\"color:#a50\">'setCookie'<\/span>, (name, value, options) =&gt; {\n  <span style=\"color:#00a\">if<\/span> (name !== mockSingleData.cookieName) fail(<span style=\"color:#a50\">'setCookie called with incorrect cookie name'<\/span>);\n  <span style=\"color:#00a\">if<\/span> (value !== <span style=\"color:#a50\">'false'<\/span>) fail(<span style=\"color:#a50\">'setCookie not called with \"false\"'<\/span>);\n  <span style=\"color:#00a\">if<\/span> (options.domain !== mockSingleData.cookieDomain) fail(<span style=\"color:#a50\">'setCookie called with incorrect cookie domain'<\/span>);\n  <span style=\"color:#00a\">if<\/span> (options[<span style=\"color:#a50\">'max-age'<\/span>] !== mockSingleData.cookieExpires * <span style=\"color:#099\">24<\/span> * <span style=\"color:#099\">60<\/span> * <span style=\"color:#099\">60<\/span>) fail(<span style=\"color:#a50\">'setCookie called with incorrect max-age'<\/span>);\n});\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>runCode(mockSingleData);\n\nassertApi(<span style=\"color:#a50\">'generateRandom'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'getCookieValues'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'makeInteger'<\/span>).wasNotCalled();\nassertApi(<span style=\"color:#a50\">'setCookie'<\/span>).wasCalled();\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the tag finished successfully.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertApi(<span style=\"color:#a50\">'gtmOnSuccess'<\/span>).wasCalled();\n<\/code><\/pre>\n<\/div>\n<p>The magic happens in the <code>setCookie()<\/code> mock. Basically, we fail the test if the <code>setCookie()<\/code> API is called with values that do not correspond to those we set in the mock data object.<\/p>\n<h4 id=\"test-5-set-multi-cookie-to-a-when-equal-to-33\">Test 5: Set multi cookie to A when equal to 33<\/h4>\n<p>When we start testing the <strong>multi<\/strong> distribution options, we are basically checking if the randomized number falls into the distributions established in the tag settings.<\/p>\n<p>We\u2019ll first test the simplest scenario: the randomizer picks the first group the user defined. We use this by setting it to the upper bound of the first group.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">mock(<span style=\"color:#a50\">'getCookieValues'<\/span>, []);\nmock(<span style=\"color:#a50\">'generateRandom'<\/span>, <span style=\"color:#099\">33<\/span>);\nmock(<span style=\"color:#a50\">'setCookie'<\/span>, (name, value, options) =&gt; {\n  <span style=\"color:#00a\">if<\/span> (value !== <span style=\"color:#a50\">'A'<\/span>) {\n    fail(<span style=\"color:#a50\">'setCookie not called with \"A\"'<\/span>);\n  }\n});\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>runCode(mockMultiData);\n\nassertApi(<span style=\"color:#a50\">'generateRandom'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'getCookieValues'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'makeInteger'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'setCookie'<\/span>).wasCalled();\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the tag finished successfully.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertApi(<span style=\"color:#a50\">'gtmOnSuccess'<\/span>).wasCalled();\n<\/code><\/pre>\n<\/div>\n<p>The setup is very similar to the previous single distribution tests, with the difference that the cookie <strong>value<\/strong> is now established in the mock data object, and we expect the <code>makeInteger()<\/code> API to be called for the template test to succeed.<\/p>\n<h4 id=\"test-6-set-multi-cookie-to-b-when-equal-to-34\">Test 6: Set multi cookie to B when equal to 34<\/h4>\n<p>This test is practically identical to the previous one, with the exception that we set the <code>generateRandom()<\/code> API to return a value that sits within the lower bound of the second group of the mock object, and we modify the <code>setCookie()<\/code> API to check for the corresponding value (<code>'B'<\/code>) instead.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">mock(<span style=\"color:#a50\">'getCookieValues'<\/span>, []);\nmock(<span style=\"color:#a50\">'generateRandom'<\/span>, <span style=\"color:#099\">34<\/span>);\nmock(<span style=\"color:#a50\">'setCookie'<\/span>, (name, value, options) =&gt; {\n  <span style=\"color:#00a\">if<\/span> (value !== <span style=\"color:#a50\">'B'<\/span>) {\n    fail(<span style=\"color:#a50\">'setCookie not called with \"B\"'<\/span>);\n  }\n});\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>runCode(mockMultiData);\n\nassertApi(<span style=\"color:#a50\">'generateRandom'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'getCookieValues'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'makeInteger'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'setCookie'<\/span>).wasCalled();\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the tag finished successfully.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertApi(<span style=\"color:#a50\">'gtmOnSuccess'<\/span>).wasCalled();\n<\/code><\/pre>\n<\/div>\n<h4 id=\"test-7-do-not-set-multi-cookie-if-not-within-distributions\">Test 7: Do not set multi cookie if not within distributions<\/h4>\n<p>In case the randomizer does not fall into any of the probability ranges established in the <strong>multi<\/strong> distribution settings, the cookie should not be set.<\/p>\n<p>The easiest way to check this is by making sure the <code>setCookie()<\/code> API was not called. You can do this with <code>assertApi('setCookie').wasNotCalled()<\/code>, but you can also mock the <code>setCookie()<\/code> API to <strong>fail<\/strong> if called. I\u2019ve opted to do both just for giggles.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">mock(<span style=\"color:#a50\">'getCookieValues'<\/span>, []);\nmock(<span style=\"color:#a50\">'generateRandom'<\/span>, <span style=\"color:#099\">67<\/span>);\nmock(<span style=\"color:#a50\">'setCookie'<\/span>, (name, value, options) =&gt; {\n  fail(<span style=\"color:#a50\">'setCookie should not have been called'<\/span>);\n});\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>runCode(mockMultiData);\n\nassertApi(<span style=\"color:#a50\">'generateRandom'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'getCookieValues'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'makeInteger'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'setCookie'<\/span>).wasNotCalled();\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the tag finished successfully.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertApi(<span style=\"color:#a50\">'gtmOnSuccess'<\/span>).wasCalled();\n<\/code><\/pre>\n<\/div>\n<p>Here, we generate a random number that is above <strong>66<\/strong> to make sure the number doesn\u2019t belong to any group. Then, we mock <code>setCookie()<\/code> to fail the test if ever called (regardless of what values it was called with), and we also use <code>assertApi()<\/code> to pass the test only if the API was not called.<\/p>\n<h4 id=\"test-8-set-multi-cookie-with-correct-options\">Test 8: Set multi cookie with correct options<\/h4>\n<p>This is similar to <strong>Test 4<\/strong>, since we are just checking that the cookie is set with options that correspond with those configured in the template settings (or in the mock data object as in this case). The test will fail if there are any differences.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">mock(<span style=\"color:#a50\">'getCookieValues'<\/span>, []);\nmock(<span style=\"color:#a50\">'generateRandom'<\/span>, <span style=\"color:#099\">20<\/span>);\nmock(<span style=\"color:#a50\">'setCookie'<\/span>, (name, value, options) =&gt; {\n  <span style=\"color:#00a\">if<\/span> (name !== mockMultiData.cookieName) fail(<span style=\"color:#a50\">'setCookie called with incorrect cookie name'<\/span>);\n  <span style=\"color:#00a\">if<\/span> (value !== mockMultiData.multiPercent[<span style=\"color:#099\">0<\/span>].value) fail(<span style=\"color:#a50\">'setCookie called with incorrect cookie value'<\/span>);\n  <span style=\"color:#00a\">if<\/span> (options.domain !== mockMultiData.cookieDomain) fail(<span style=\"color:#a50\">'setCookie called with incorrect cookie domain'<\/span>);\n  <span style=\"color:#00a\">if<\/span> (options[<span style=\"color:#a50\">'max-age'<\/span>] !== mockMultiData.cookieExpires * <span style=\"color:#099\">24<\/span> * <span style=\"color:#099\">60<\/span> * <span style=\"color:#099\">60<\/span>) fail(<span style=\"color:#a50\">'setCookie called with incorrect max-age'<\/span>);\n});\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Call runCode to run the template's code.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>runCode(mockMultiData);\n\nassertApi(<span style=\"color:#a50\">'generateRandom'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'getCookieValues'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'makeInteger'<\/span>).wasCalled();\nassertApi(<span style=\"color:#a50\">'setCookie'<\/span>).wasCalled();\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Verify that the tag finished successfully.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>assertApi(<span style=\"color:#a50\">'gtmOnSuccess'<\/span>).wasCalled();\n<\/code><\/pre>\n<\/div>\n<h2 id=\"summary\">Summary<\/h2>\n<p>In this article, I showed you two walkthroughs for how to write tests for your variable and tag templates.<\/p>\n<p>A good way to get started is to figure out all the branches of your code, and then write tests that check for all the permutations.<\/p>\n<p>To reduce the number of tests you need, make sure you utilize the <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/#field-configuration-reference\">field configurations<\/a> (particularly the <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/#validation-rules\">validation rules<\/a>) efficiently. By blocking invalid input before the tag or variable can even be saved you\u2019re reducing the number of validation checks you need to write (and test for) in the code.<\/p>\n<p>Hopefully we\u2019ll get some <strong>test coverage<\/strong> details into the UI, so that you can plan your tests to cover all possible branches and features of the template code.<\/p>\n<p>Finally, <strong>remember to update the tests when you update the template itself<\/strong>. Writing and updating tests should become part and parcel of your quality assurance process when writing templates. You owe it to the users who end up installing your template into their Google Tag Manager containers!<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Google Tag Manager now lets you add unit tests directly to your custom templates. This is useful, since it allows you to control the code stability of your templates, especially if you\u2019ve decided to share those templates with the public. I recently shared a general guide for how template tests work, but I wanted to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":92789,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[834,14492,4196,11326,17477],"dealstore":[],"offerexpiration":[],"class_list":["post-92788","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-custom","tag-gtmtips","tag-template","tag-tests","tag-walkthrough"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>#GTMTips: Walkthrough Of Custom Template Tests - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=92788\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"#GTMTips: Walkthrough Of Custom Template Tests - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"Google Tag Manager now lets you add unit tests directly to your custom templates. This is useful, since it allows you to control the code stability of your templates, especially if you\u2019ve decided to share those templates with the public. I recently shared a general guide for how template tests work, but I wanted to [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=92788\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-17T02:57:45+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"960\" \/>\n\t<meta property=\"og:image:height\" content=\"540\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"16 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=92788#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=92788\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"#GTMTips: Walkthrough Of Custom Template Tests\",\"datePublished\":\"2025-02-17T02:57:45+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=92788\"},\"wordCount\":2292,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=92788#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg\",\"keywords\":[\"Custom\",\"GTMTips\",\"Template\",\"tests\",\"walkthrough\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=92788#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=92788\",\"url\":\"https:\/\/fivemor.com\/?p=92788\",\"name\":\"#GTMTips: Walkthrough Of Custom Template Tests - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=92788#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=92788#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg\",\"datePublished\":\"2025-02-17T02:57:45+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=92788#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=92788\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=92788#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg\",\"width\":960,\"height\":540},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=92788#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"#GTMTips: Walkthrough Of Custom Template Tests\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"#GTMTips: Walkthrough Of Custom Template Tests - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=92788","og_locale":"en_US","og_type":"article","og_title":"#GTMTips: Walkthrough Of Custom Template Tests - Som2ny Network","og_description":"Google Tag Manager now lets you add unit tests directly to your custom templates. This is useful, since it allows you to control the code stability of your templates, especially if you\u2019ve decided to share those templates with the public. I recently shared a general guide for how template tests work, but I wanted to [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=92788","og_site_name":"Som2ny Network","article_published_time":"2025-02-17T02:57:45+00:00","og_image":[{"width":960,"height":540,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"16 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=92788#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=92788"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"#GTMTips: Walkthrough Of Custom Template Tests","datePublished":"2025-02-17T02:57:45+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=92788"},"wordCount":2292,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=92788#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg","keywords":["Custom","GTMTips","Template","tests","walkthrough"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=92788#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=92788","url":"https:\/\/fivemor.com\/?p=92788","name":"#GTMTips: Walkthrough Of Custom Template Tests - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=92788#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=92788#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg","datePublished":"2025-02-17T02:57:45+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=92788#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=92788"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=92788#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/walkthrough-custom-template-tests.jpg","width":960,"height":540},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=92788#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"#GTMTips: Walkthrough Of Custom Template Tests"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/92788","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=92788"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/92788\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/92789"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=92788"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=92788"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=92788"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=92788"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=92788"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}