Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I unit test an event listener on an input field?

In the simplest test possible, I'm attempting to test the following function:

addPercentSign: function (oEvent, control) {
      var inputVal = oEvent.getParameters().value;
      var inputNumber = parseFloat(inputVal);

            if (inputNumber) {

                if (inputNumber < 50 || inputNumber > 100) {
                    //see learningCurveFormatCheck
                    return null;
                } else {
                    var finalVal = inputNumber.toFixed(1);
                    var finalOutput = finalVal + "%";

                    control.learningCurve.setValue(finalOutput);

                    return finalOutput;
                };
            }
        }

The above function is an event listener on an input field (id="learningCurveInput"). When a user types a value into the field and then triggers a 'submit' event (via "ENTER" keypress), the 'addPercentSign' function gets called.

I understand that with unit tests, the idea is to 'isolate' the test as much as possible from any dependencies. Therefore, to test against a DOM manipulation, one can attach the element to the test.html under a div like so:

$('<input id="learningCurveInput" type="text"/>').appendTo('#qunit-fixture');

Can anyone explain what to do next here? The function relies on the Event object getting passed in to retrieve the input value. I'm not sure how to recreate that within the test. I've attached my unit test below, but it's just to show my thinking:

...,
    function (formatter, viewControls) {
        "use strict";

        QUnit.module("Formatter Object Exists")

        QUnit.test("Learning Curve Input Value", function (assert) {

            $('<input id="learningCurveInput" type="text"/>').appendTo('#qunit-fixture');

            $("#learningCurveInput").val("55");

            var result = '55';

            equals(result, $('#learningCurveInput').val(), "testing input value"); 
        });

         QUnit.test("addPecentSign Function", function (assert) {
              //how to test this dom-dependent function?
        });

    }
);

Summary Question

How can I unit test the 'addPercentSign' function that is called on 'submit' of an input field?

like image 806
Kode_12 Avatar asked Dec 09 '16 21:12

Kode_12


1 Answers

I'd suggest splitting this up in these parts:

  • Test the conversion from input to output, i.e.: "51" -> "51.0%"
  • Test if your method of modifying the input value works
  • Test whether your attached event listener gets called when it's needed

If all of these tests succeed, you can assume chaining them together will work as well.

To test the conversion method, I'd suggest moving its logic into a separate, pure function. I've pasted your format logic below and removed the setValue side effect. I included some tests (you should check them out and see if you need more/they match your requirements). I've left two failing tests as an example.

String formating test:

function addPercentSign(val) {
  var inputNumber = parseFloat(val);

  if (inputNumber) {
    if (inputNumber < 50 || inputNumber > 100) {
      //see learningCurveFormatCheck
      return null;
    } else {
      var finalVal = inputNumber.toFixed(1);
      var finalOutput = finalVal + "%";

      return finalOutput;
    };
  };
};

module("String formatting");

test("Returns undefined for unparseable strings", function() {
	["foo", null, NaN, "0.0.0"]
    .forEach(function(result, i, arr) {
    	var result = addPercentSign(result);
    	strictEqual(result, undefined, arr[i] + " produces " + result);
    });
});

test("Returns null for values below 50 and above 100", function() {
	["0", "0.0", "25", "49.99999", "100.00000001", "10300", Infinity]
  	.forEach(function(result, i, arr) {
    	var result = addPercentSign(result);
    	strictEqual(result, null, arr[i] + " produces " + result);
    });
});

test("Appends a % sign for values between 50 and 100", function() {
	strictEqual(addPercentSign("51.0"), "51.0%");
	// ...
});

test("Appends a digit for values without one", function() {
	strictEqual(addPercentSign("51"), "51.0%");
	// ...
});

test("Removes and rounds digits for values with more than one", function() {
	strictEqual(addPercentSign("51.999"), "52.0%");
  	strictEqual(addPercentSign("51.06"), "51.1%");
	// ...
});
<link href="https://code.jquery.com/qunit/qunit-1.12.0.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/qunit/qunit-1.12.0.js"></script>

<div id="qunit"></div>
<div id="qunit-fixture"></div>

Now that you've got this method covered, you'll need to find out if you can retrieve values from and write values to an input field. Those are similar to the one you already wrote:

Getting and setting the input's value

function createInput() {
  return $('<input id="learningCurveInput" type="text" value="hello world"/>')
    .appendTo('#qunit-fixture');
}

module("Writing and reading to input", {})

test("writes to value", function(assert) {
  var $input = createInput();
  var result = "55";

  // Whatever you're using to set:
  // in your code, I read `control.learningCurve.setValue`
  // if that's what you're using, that's the method you should test
  $input.val(result);

  strictEqual($input.val(), result);
});

test("reads from value", function(assert) {
  var $input = createInput();
  
  // Whatever you're using to get the value
  $input.val();

  strictEqual($input.val(), "hello world");
});
<link href="https://code.jquery.com/qunit/qunit-1.12.0.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/qunit/qunit-1.12.0.js"></script>

<div id="qunit"></div>
<div id="qunit-fixture"></div>

Now that you know you can (a) correctly transform values, and (b) correctly read and write values, you'll need to test if your get value -> transform value -> set value sequence will be triggered by the correct input. For example:

Testing the event listeners

jQuery has some handy methods for attaching and triggering event listeners. You can use .change or submit without an argument to mimic UI input. Alternatively, you can trigger click on a submit button.

function createForm() {
  return $("<form></form>")
    .append(createInput());
}

function createInput() {
  return $('<input id="learningCurveInput" type="text" value="hello world"/>')
    .appendTo('#qunit-fixture');
}

module("Form event listeners", {})

test("input executes method on change", function(assert) {
  var called = false;
  var onChange = function() { called = true; };
  var $input = createInput();
  $input.change(onChange);
  
  $input.val("test");
  strictEqual(called, false);
  
  $input.change();
  strictEqual(called, true);
  
});

test("form executes method on submit", function(assert) {
  var called = false;
  var onSubmit = function() { called = true; };
  var $form = createForm();
  var $input = $form.find("input");
  
  $form.submit(onSubmit);
  
  strictEqual(called, false);
  
  $form.submit();
  strictEqual(called, true);
  
});
<link href="https://code.jquery.com/qunit/qunit-1.12.0.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/qunit/qunit-1.12.0.js"></script>

<div id="qunit"></div>
<div id="qunit-fixture"></div>

Concluding

Now, you can determine if an implementation of your code is covered by your tests:

$("form").submit(function() {                   // Tested in test 3
  var $input = $(this).find("input");
  var originalValue = $input.val();             // Tested in test 2
  var newValue = addPercentSign(originalValue); // Tested in test 1
  $input.val(newValue);                         // Tested in test 2
});

Notice that it's mainly the first test module that has custom logic and requirements. If you're using jQuery, which is already heavily tested, you won't need to re-implement tests for methods such as .val(): check their public repo to see the coverage for those. If you're implementing custom methods to interact with the DOM, you do need to test them.

So, in short: rewrite addPercentageSign to be a pure function that takes a string and returns a string; make sure it's thoroughly tested. Interact with the DOM via a tested library, or write tests for both DOM editing and event listening.

like image 70
user3297291 Avatar answered Nov 14 '22 21:11

user3297291