Categories
Testing

JavaScript Problems — Click Events, Unit Tests, Page Load, and More

Like any kind of apps, there are difficult issues to solve when we write JavaScript apps.

In this article, we’ll look at some solutions to common JavaScript problems.

Tools for Unit Testing JavaScript Code

There are many test runners for creating and running unit tests with JavaScript apps.

Ava is a fast test runner that lets us run unit tests. It supports promises and ES6 syntax. Also, the environment is isolated for each file.

It also supports generators and async functions. Observables are also supported. Stack traces are easier to read than other runners as it’s clean.

Buster.js is another test runner. It’s cross-platform and it runs on Windows, Mac, and Linux. Tests run from a browser headlessly. Multiple clients can run the tests at the same time. Node apps can also be tested. xUnit or BDD style tests can be written.

Multiple JavaScript test frameworks are supported. It comes with SinonJS. Tests are run automatically on save.

Jasmine is a BDD test framework. The syntax of it looks like the RSpec syntax, which is used to test Ruby and Rails apps.

QUnit lets us test JavaScript front end code. It’s mainly based on jQuery, jQuery UI, and jQuery Mobile.

Sinon is another testing tool. It’s a simple test runner that lets us mock code and run them.

It has no dependencies.

Jest is another powerful test framework with a built-in test runner and many tools for mocking things to help us tets. It supports all modern syntax, It supports async and modules, including mocking them.

JavaScript Static Variables

We can add static variables by directly attaching a property directly to the constructor.

For instance, if we have:

function MyClass () {  
  //...  
}

Then we can write:

MyClass.staticProp  = "baz";

We attach staticProp to MyClass directly.

Check that a Number is a Float or an Integer

We can check if a number is an integer by using the remainder operator to get the remainder of it when we divide it by 1.

For example, we can write;

const isInt = (n) => {  
  return n % 1 === 0;  
}

If it’s an integer, then it’s true .

We can check if it’s a floating-point number by changing === to !== . We write:

const isFloat = (n) => {  
  return n % 1 !== 0;  
}

Check Which Key is Pressed on the Keyboard

We can use the keypress event’s keyCode or which property.

They both have the integer code for the keyboard key that is pressed.

For example, we can write:

const code = e.keyCode || e.which;  
if(code === 13) {   
  //Do something  
}

We check if it’s 13 to check for the Enter key.

window.onload vs document.onload

There is a difference between window.onload and document.onload .

window.onload is fired when the entire page loads, including all content like images, CSS, scripts, and other things.

documebt.onload is called when the DOM is ready which can be prior to images and other external content is loaded.

Self-References in Object Literals

We can use this to reference the object that this is currently in.

For instance, if we have:

const foo = {  
  a: 1,  
  b: 2,  
  get c() {  
    return this.a + this.b;  
  }  
}

Then this.a is 1 and this.b is 2, so c is 3.

We can only do this with top-level methods, including getters and setters.

Otherwise, the value of this maybe different.

addEventListener vs onclick

To attach a click listener to an element, we can do it a few ways.

We can call addEventListener with the 'click' event.

Also, we can add the onclick attribute to an element.

We can also get the element and set a callback as the value of the onclick property.

For example, we can write:

element.addEventListener('click', onClick, false);

onClick is a function.

false disables capture mode, which propagates events from the root element down.

Likewise, we can write:

<a href="#" onclick="alert('hello');">click me</a>

We have the a element with the onclick attribute that has JavaScript expressions in it.

Likewise, we can write:

element.onclick = () => {   
  //...  
};

We set a listener function to the onclick method of a DOM element object.

They do the same thing.

Conclusion

We can attach click listeners in various ways,

Also, we can use this to reference itself in top-level methods.

There are also many tools for writing and running unit tests for JavaScript apps.

Categories
Testing

How Do We Write Unit Tests to Test Our Code?

If we want to change our code without worry about breaking anything, then having unit tests is important.

In this article, we’ll look at how to write unit tests so that we can have peace of mind when changing code.

Writing Unit Tests

Unit tests shouldn’t be in a far-away corner of the source tree. It should be conveniently located so that we can easily look for them and change them is necessary.

For small projects, we can embed the tests in the module itself.

For larger projects, we can move them into their own subdirectory.

By writing unit tests, we’re providing developers who look at our code with 2 invaluable resources.

They include examples of how to use the functionality of our module. And a means to build regression tests to validate future change of our code.

Therefore. it’s great for documentation of our code and also makes updating the tests later easier for everyone.

A unit test will call a function with an argument and check against the results returned.

We must run them often so we’re checking if our stuff is still working all the time.

Using Test Harnesses

Test harnesses are standard sets of data and code that we use to run our tests.

It’s used to handle common things like logging, analyzing expected results, and selecting and running tests.

Test runners probably have lots of these functionalities of already so we can just use them.

However, if they aren’t present in our testing framework, then we’ve to write them ourselves.

We can create a base class that provides these common operations. Individual tests can be a subclass of the hardness class so that we can just call the methods in the test class.

This is good because it meets the DRY principle. We aren’t repeating any code for logging and checking results.

A harness should provide a standard way of setting up our tests and cleaning up after it’s run.

It should have a method for selecting individual tests or all available tests.

Also, it should have a means of analyzing output from expected or unexpected results.

Failure reporting should also be standard in our test harness.

Tests should be composable. This means that a test can be composed of subtests of subcomponents of any depth.

We may add tests to help us debug our code. This may be a logging statement or a code that interacts with the IDE.

Once we’re done debugging, then we formalize the test. If we break it once then it’s likely to break again, so we should just add a test to make sure that it doesn’t break again.

Build a Test Window

We should test our software once it’s been deployed with real-world data flowing through it,

We can do that with our own end-to-end tests. There’re many ways to make our app run like how a user would have interacted with it.

First, we need clean seed data populated inside our app. Then we need to write tests with frameworks like Selenium to interact with our app like how users would have interacted with it.

Once we’re done with the tests, we reset the data to the original seed data so that our tests will run consistently.

A Culture of Testing

All software that we write will tested by someone including our test and users. Therefore, we should make sure that we have to test it thoroughly before we release it into any environment.

This minimizes maintenance costs and customer service calls.

Therefore, testing should be a habit. If we don’t test our stuff, then our users will.

Conclusion

We should have a culture of testing ingrained. Because if we don’t test our stuff thoroughly then our users will.

To make our lives easier, we should write unit tests and end to end tests.

This way, we can test everything automatically without thinking once the test is written.

Also. we should make sure that we use a test harness or write our own so that we can run our tests and log the results of the tests.

Categories
JavaScript Best Practices

JavaScript Clean Code — Default Parameters, Properties, and Singletons

Cleaning up our JavaScript code is easy with default parameters and property shorthands.

In this article, we’ll look at how we can use them to make our code easier to read and write.

ES6 Default Parameters

We can use default parameters syntax to set the default values of parameters.

If the argument is omitted or undefined, then the default parameter value will be set as the value of the parameter.

For instance, we can use it as follows:

const add = (a, b = 2) => a + b;

Then when we call add as follows:

const result = add(1);

result is 3 since b is 2 and a is 1.

This is much better than writing:

const add = (a, b) => {
  b = b || 2;
  return a + b;
}

Since we don’t have to change the value of b and write 2 lines instead of 1.

ES6 Property Shorthands

Property shorthands are great because they let us add properties to our code JavaScript in a shorter way.

For instance, if we have:

const a = 1,
  b = 'foo',
  c = 3;

Instead of writing:

const obj = {
  a: a,
  b: b,
  c: c
}

We write:

const obj = {
  a,
  b,
  c
}

Both mean the same thing. It’s just that the shorthand is much shorter than the original version.

Now obj.a is 1, obj.b is 'foo' and obj.c is 3.

Singletons

A singleton is an object that can be the only instance of a class.

We can use that to manage anything global like app-wide state.

With ES6 or newer, we can just create a constant and then prevent the object from changing with Object.freeze .

For instance, we can write the following:

const data = [];
const Store = {
  add: item => data.push(item),
  getById: id => data.find(d => d.id === id)
}

Object.freeze(Store);

In the code above, we have the Store object with the add and getById methods.

The add method adds a new item to the end of the array, and getById finds the item by ID.

Then we call Object.freeze with the Store object to freeze the object in place.

We can also encapsulate data by putting it in a class as follows:

class Store {
  constructor() {
    this.data = [];
  }

  add(item) {
    this.data.push(item);
  }

  getById(id) {
    return this.data.find(d => d.id === id);
  }
}

const store = new Store();
Object.freeze(store);

In the code above, we have the Store class, which has the same methods as the Store object in the previous example.

Then we create a new store and then freeze it in place.

Now we have an immutable instance, but we can still create more than one instance of Store .

Therefore, we’ve to make that the constructor always returns the same instance.

To do that, we write:

class Store {
  constructor() {
    if (!Store.instance) {
      this.data = [];
      Store.instance = this;
    }
    return Store.instance;
  }

  add(item) {
    this.data.push(item);
  }

  getById(id) {
    return this.data.find(d => d.id === id);
  }
}

const store = new Store()
Object.freeze(store);

We have the following constructor:

constructor() {
  if (!Store.instance) {
    this.data = [];
    Store.instance = this;
  }
  return Store.instance;
}

We can return whatever we want when we instantiate a JavaScript class, so we can make sure that we always return the same instance by setting this to Store.instance .

Store.instance is static so it’s shared by all instances of the class.

Therefore, we can return it if it’s defined.

Conclusion

Default parameters are great for shortening our code so that we don’t have to set the parameter’s value with the || operator.

Instead, we just set the default value in the function signature.

The property shorthand is great because we don’t have to write out the colon all the time, we just list the parameter names and the values will be set if a variable or constant in the scope and has the same name as the property.

With the singleton pattern, we can always return the same instance of an object.

We just create an object and freeze it or we create a class that always returns the same instance of an object.

Categories
JavaScript Best Practices

JavaScript Clean Code — Name and Test Heuristics

Bad code has lots of unique characteristics. In this article, we’ll look at each one and what they are. We look at more general code smells.

In this article, we’ll look at how to name things properly, and to write a test that has high coverage and fast.

Naming

Use Long Names for Long Scopes

If the scope is long then the name should be longer than if they’re in a short scope.

Variables with short names lose their meaning over long distances.

Avoid Encodings

Names shouldn’t be encoded with scope information. We should avoid useless prefixes.

However, types in names can be useful in JavaScript since variables and constants have dynamic types.

Names Should Describe Side Effects

If a piece of code commits side effects, the name should reflect it. For example, if we have the following function:

let foo;  
const getFoo = () => {  
  if (!foo) {  
    foo = {}  
  }  
  return foo  
}

Then we should name it getFoo because we didn’t describe the obvious side effect in the name.

Instead, we should name it setFooIfNotExistAndGetFoo to take both actions into account.

Tests

Insufficient Tests

We should make sure that we have sufficient test coverage so that we know the code and branches work.

Also, our tests should take into account both positive and negative cases.

Boundary and edge cases should also have tests to test cases that may break our code.

Use a Coverage Tool

Test coverage tools tell us what we have tests for and what still needs test coverage.

They also tell us which branches of our code have test coverage.

Don’t Skip Trivial Tests

They’re easy to write and serves as good documentation.

An Ignored Test is a Question About an Ambiguity

An ignored test is always questionable. We don’t know why they’re skipped so we sure make the reason clear why we skip a test.

Test Boundary Conditions

Testing boundary conditions is important since code often breaks around boundary conditions.

Exhaustively Test Near Bugs

If a piece of code has bugs, then we should test those cases more.

Patterns of Failures are Revealing

Patterns that test cases fail can tell us a lot about what’s breaking our code.

Cases that fail tell usually have some pattern, like when they fail with 5 or more characters passed in or something like that.

Tests Should be Fast

Slow tests are torture to run. They just get ignored and won’t get run. We should make them as fast as possible, especially since there’s going to be lots of them.

Conclusion

We should name things in an unambiguous way. If there’re side effects, then we should describe them.

Also, tests should cover bugs and boundary cases and they should run fast.

We can use a coverage tool to get a clear idea of what pieces of code are covered by tests.

Categories
JavaScript Best Practices

JavaScript Clean Code — More Heuristics

Bad code has lots of unique characteristics. In this article, we’ll look at each one and what they are. We look at more general code smells.

Don’t Be Arbitrary

We should structure our code to follow clean code conventions. The code that doesn’t belong in the place shouldn’t be there.

If a team has a convention for the codebase, then everyone should stick to it. Otherwise, it gets messy fast.

Encapsulate Boundary Conditions

Boundary conditions should be put in a function or variable for easy access and understanding.

For example, we should set arr.length — 1 to a variable as follows if we have wanted it to be the end index variable of the loop:

const arr = [1, 2, 3];
const lastIndexOfArray = arr.length - 1;
for (let i = 0; i <= lastIndexOfArray; i++) {
  console.log(arr[i]);
}

We can see that once we assigned to arr.length — 1 to the constant lastIndexOfArray, then we know that it is the last index of the array.

We no longer have to think about what it means. From the constant name, we know what it means.

Similarly, we should do it for other boundary cases, so we don’t have to guess why we have +1 or -1 in different places.

Function Should Descend Only One Level of Abstraction

This means that functions should only do one thing. If we require it to do another thing at a different level of abstraction, then we should write a new function and call that.

Since functions should be small and should only do one thing, they shouldn’t be touching different things at different levels.

Keep Configurable Data at High Levels

Keeping configuration data at a high level keep them within our sight. Since we’re using them in many places, it makes sense for them to be at a high level.

If we put them at a high level, then they’re also easy to change.

Avoid Transitive Navigation

We should avoid code structure where we have A referencing B and B referencing C.

It’s harder on our brains to understand this kind of navigation. Also, it exposes us to more code that is coupled together.

We shouldn’t expose code that we don’t want to expose.

For example, something like:

foo.getBar().getBaz().doSomething();

The code above is bad because we have to get the Bar instance with getBar, and then with the Bar instance, we have to get the Baz instance with getBaz. Then we finally call doSomething on the Baz instance.

That’s fragile code, because if anything in the chain breaks, then the whole thing breaks.

Any of them changing will become an issue. We should take out this transitive navigation code by changing the implementation so that we don’t have this kind of structure.

Choose Descriptive Names

Naming things descriptively is important. Names tell us a lot about what a piece of code is doing. Therefore, we should choose names that tells what it’s storing or doing.

Names like x and y aren’t good names for variables since they tell us nothing about what they store.

On the other hand, numApples and numOranges are better names since they do tell us what they’re doing.

Choose Names at the Appropriate Level of Abstraction

We should think about the level of abstraction of our code when we name things with names.

For example, if we have the following Phone class:

class Phone {
  dial() {}
}

Then we are being too specific with our naming of the dial method when we actually want to convey that we’re using it to call another person.

Most phones don’t have dials anymore so it wouldn’t really make sense to name them as such. Some phones have keypads and smartphones have screens.

So instead, we should rename it to be more general as follows:

class Phone {
  call() {}
}

Unambiguous Names

Names shouldn’t be ambiguous. They shouldn’t have double meaning where people have to guess what the name actually means.

Guessing is bad since people may lead to the wrong conclusion and make mistakes when changing the code.

For example, if we want to create a function to rename a file, we shouldn’t call it rename since it doesn’t tell us what we’re renaming. Instead renameFile is more appropriate since we know for sure that the function is for renaming a file.

Conclusion

We should be clear when we’re naming things. Names should be descriptive and unambiguous.

Also, we should name things at the correct level of abstraction, so names that should be general should be generic, and the opposite is also true.

Transitive navigation is also bad because it creates fragile code. We shouldn’t have a chain of function calls that get different types of objects in order to do something.

Finally, we should encapsulate boundary conditions so we’re clear of what it is and why we have it.