Override Default Properties with JavaScript

By  on  

Unit testing with client side JavaScript is something you don't do until you're made to.  Of course unit testing is important but let's be honest:  most people are just happy that their code works, right?  Anyways, fast forward to a world where unit testing is normal and we have a problem to solve:  overriding native browser property values for the sake of unit testing.  The following is an easy strategy for overriding default browser property values!

The JavaScript

You can't successfully override all properties with a simple statement; let's use navigator.userAgent for example:

console.log(navigator.userAgent);
// >> Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36

// Try to set that value -- will be unsuccessful
navigator.userAgent = 'Walshbot';

console.log(navigator.userAgent);
// >> Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36

Overriding the navigator.userAgent can be useful for the purposes of unit and functional testing, so how can we successfully change that value?  With Object.defineProperty:

// Store the original value
var originalUserAgent = navigator.userAgent;

// Override!
Object.defineProperty(navigator, 'userAgent', {
    get: function() {
        return 'WalshBot';
    }
});

// (Run your tests here)

// Set the value back to original
Object.defineProperty(navigator, 'userAgent', {
    get: function() {
        return originalUserAgent;
    }
});

Within the snippet above, we save the original Object.defineProperty value, override it briefly, then set the value back to the original.  Object.defineProperty is very useful, especially when you consider that the second argument is a function -- you can execute any logic within that function to return the proper value at the time it's requested!

Recent Features

  • By
    LightFace:  Facebook Lightbox for MooTools

    One of the web components I've always loved has been Facebook's modal dialog.  This "lightbox" isn't like others:  no dark overlay, no obnoxious animating to size, and it doesn't try to do "too much."  With Facebook's dialog in mind, I've created LightFace:  a Facebook lightbox...

  • By
    Responsive Images: The Ultimate Guide

    Chances are that any Web designers using our Ghostlab browser testing app, which allows seamless testing across all devices simultaneously, will have worked with responsive design in some shape or form. And as today's websites and devices become ever more varied, a plethora of responsive images...

Incredible Demos

  • By
    Downloadify:  Client-Side File Generation Using JavaScript and Flash

    The following tools is in its very beta stages and works intermittently. Its so damn useful that I had to show it off now though! I recently stumbled upon Downloadify, a client-side file generation tool based on JavaScript and Flash ActionScript code. A...

  • By
    Create a Context Menu with Dojo and Dijit

    Context menus, used in the right type of web application, can be invaluable.  They provide shortcut methods to different functionality within the application and, with just a right click, they are readily available.  Dojo's Dijit frameworks provides an easy way to create stylish, flexible context...

Discussion

  1. Valtteri

    Why not this:

    Object.defineProperty(navigator, 'userAgent', {
        get() {
            return 'WalshBot';
        }
    });

    It’s standard and seems to work fine.

  2. Useful but it should be treated carefully, because what would it happen if the test fails and we don’t reassign the original value?

  3. Interesting as this can be used to invoke functions without any ().

    Here I am counting the number of times a property is requested;

    'use strict';
    
    let phelm = {
      getCount: 0,
      hasStore: ''
    };
    
    phelm.__defineGetter__('is', function() {
      this.getCount ++;
      return 'cool';
    });
    
    console.log(phelm.getCount);
    console.log(phelm.is);
    console.log(phelm.getCount);
    
  4. Hi,

    __defineGetter__ is deprecated.

    Another way: IE9+

    var originalUserAgent = navigator.userAgent;
    
    Object.defineProperty(navigator, 'userAgent', {
      value: 'Walshbot',
      writable: true,
      configurable: true,
      enumerable: true
    })
    
    console.log(navigator.userAgent) // Walshbot 
    
    Object.defineProperty(navigator, 'userAgent', {
      value: originalUserAgent,
      writable: true,
      configurable: true,
      enumerable: true
    })
    
  5. Jason

    Doesn’t seem to work cross-domain, only seems to work in frames with local content.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!