Categories
Testing

Optimal Selector Strategies for End to End Tests

To write useful end to end tests that are stable and reliable, we have to select the correct elements all the time.

In this article, we’ll look at the optimal selector strategies to do that.

IDs

IDs are the best selectors since there’s only one of them on the page.

If it’s there, then you can select it and it’ll return the right element.

If e don’t have IDs for the element we want to select, then we should add an id attribute to the element we want to select.

We can select an ID with the # sign. For instance, we can write #foo to select an element with ID foo.

CSS and Xpath Locators

If we don’t have IDs, then we can select an element with CSS selectors.

They include classes, which is the value of the class attribute.

For instance, if we have:

<div id=”main”>
  <p>Introduction</p>
  <ul>
    <li> Item 1</li>
  </ul>
</div>

and we want to select the li element, then we have to use CSS selectors to select the element.

The easiest way to get the CSS selector is to right click on the li element.

Then click on Inspect Element in the context menu. This menu item should be available in Chromium browsers and Firefox.

This should open the developer console with the li element highlighted.

Then we right click on the element, click Copy, and click on Copy Selector. This should copy the CSS selector for the li to the clipboard.

Once we did this, we get #”main” > ul > li as the CSS, which is what we want.

Alternatively, we can use the XPath to select an element with Selenium.

It’s an expression to select nodes in HTML and XML documents.

To get the XPath, we go the developer console and right click on the element and click Copy as we did before.

Then we click Copy XPath to copy the XPath. We should get //*[@id="”main”"]/ul/li as the expression copied.

This is the relative path to the li element.

We can also click Copy Full XPath on the same menu to copy the full XPath. If we do this, we get /html/body/div/ul/li copied

Using SelectorsHub

Another way to copy an XPath is to use the SelectorsHub extension.

It’s a browser addon that’s available for Chromium browsers and Firefox.

To install the addon for Chromium browsers, we go to https://chrome.google.com/webstore/detail/selectorshub/ndgimibanhlabgdgjcpbbndiehljcpfh?hl=en

Then to use it, we right click on the element, click on SelectorsHub, then click on Copy Relative XPath.

Once we do that, we get //li[normalize-space()='Item 1']

This works if the ‘Rel XPath’ option is checked in the SelectorsHub addon and the ‘ContextMenu’ option is turned on.

We can also use it to get the CSS selector of an element.

To do this, we click on Copy Rel CSSSelector to get the CSS selector of the element in the page.

If we did that with the same example HTML above, we get div[id='”main”'] ul li.

The Copy Abs XPath option lets us copy the absolute XPath, which is the same as the full XPath in the console.

If we click that, we get /html[1]/body[1]/div[1]/ul[1]/li[1] copied to the clipboard.

Fragility

CSS Selectors and XPath are more fragile than IDs since elements may change their position in the DOM tree.

If an element changes position in the tree, then the selectors or XPath used in the current test may no longer work.

Then we have a broken test and we have to go back and fix it.

If we have to use CSS selectors and XPath, then we should make the path as short as possible to reduce complexity and the chance that the path will be outdated.

Conclusion

The best selector to use for end to end tests are always IDs.

However, if we have to use CSS selectors or XPaths, we should keep them short to improve reliability.

To get selectors for elements, we can use the browser’s development console or the SelectorHub addon.

Categories
Testing

Jasmine — Custom Spies

Testing is an important part of JavaScript.

In this article, we’ll look at how to create custom spies.

Custom Spies

We can use the jasmine.setDefaultSpyStrategy to create a spy that does what we want.

For instance, we can write:

describe("spy tests", function () {
  beforeEach(function () {
    jasmine.setDefaultSpyStrategy(and => and.returnValue("Hello World"));
  }); 

  it('calls custom spy', function () {
    const spy = jasmine.createSpy();
    expect(spy()).toEqual("Hello World");
  });
});

We called setDefaultSpyStrategy with a callback that returns the 'Hello World' value.

Once we did that, our spy would return 'Hello world' .

So when we call spy , it returns 'Hello World' .

Also, we can call setDefaultSpyStrategy with no arguments to remove a custom default.

For instance, we can write;

describe("spy tests", function () {
  beforeEach(function () {
    jasmine.setDefaultSpyStrategy(and => and.returnValue("Hello World"));
  }); 

  it('calls custom spy', function () {
    const spy = jasmine.createSpy();
    expect(spy()).toEqual("Hello World");
  }); 

  it("throws if you call any methods", function () {
    jasmine.setDefaultSpyStrategy(and => and.throwError(new Error("Do Not Call Me")));
    const program = jasmine.createSpyObj(['start']);
    jasmine.setDefaultSpyStrategy(); expect(() => {
      program.start();
    }).toThrowError("Do Not Call Me");
  });
});

We called createSpyObj in the 2nd test to include a 'start' method in the porgram spy object.

Then we can check if an error is thrown with we call start .

Spying on Properties

We can spy on properties with the spyOnProperty method.

For instance, we can write:

describe("property spy test", function () {
  let someObject = {
    get myValue() {
      return 1
    }
  }; 

  beforeEach(function () {
    this.propertySpy = spyOnProperty(someObject, "myValue", "get").and.returnValue(1);
  }); 

  it("lets you change the spy strategy later", function () {
    this.propertySpy.and.returnValue(3);
    expect(someObject.myValue).toEqual(3);
  });
});

We created a someObject with the myValue getter to return something.

Then in the beforeEach hook, we call spyOnPropoerty spy on the object and return the mocked value we want.

In our test, we called returnValue to make the getter return another value.

And then we can check for the return value we just set.

We can also spy on an object with several properties on it by passing in a hash of properties.

For instance, we can write:

describe("property spy test", function () {
  it("lets you change the spy strategy later", function () {
    const obj = jasmine.createSpyObj("myObject", {}, { x: 3, y: 4 });
    expect(obj.x).toEqual(3); 
    Object.getOwnPropertyDescriptor(obj, "x").get.and.returnValue(7);
    expect(obj.x).toEqual(7);
  });
});

We create a spy object with some properties.

It has the x and y properties.

Then we can check for obj.x and return the value we want.

And then we can check for it.

Conclusion

There are different ways to spy on objects and return values.

We can create spy objects and set the spy strategy to what we want.

Categories
Testing

How to Test Node.js Apps That Interact With External APIs Using Nock

Today’s web apps rarely live alone. They often make requests to external APIs via HTTP requests.

Fortunately, testing these kinds of code isn’t too hard since we can mock the HTTP responses. This means that our tests don’t have to make the requests in our tests, which is critical for writing repeatable and independent tests.

In this piece, we’ll look at how to test an Express app that interacts with an external API.


Creating a Simple App

First, we create the app that we’ll test. The app will get a random joke from the Chuck Norris Jokes API.

Next, we create an empty project folder and run:

npm init -y

To create an empty package.json file with the default answers.

Next, we install Express and Node-Fetch to create the app and get data from APIs, respectively.

We run:

npm i express node-fetch

To install the packages.

Then we create app.js and add:

To make the app that gets the joke and returns it as the response.


Adding the Test

To add the test, we use the Jest test runner to run the test and check the results, Nock to mock the response for the request that we made in app.js, and Supertest to run the Express app and get the response that we can check.

To do this, we run:

npm i nock supertest jest

Then we create app.test.js for the test code and then add:

In the file.

The code above starts by including our app from app.js and the libraries we use for building the test.

const app = require('./app');  
const nock = require('nock');  
const request = require('supertest');

The first line above imports the Express app instance from app.js. Then the other two lines import the Nock and Supertest libraries, respectively.

Then we add the skeleton for the test by calling test from Jest and pass in the description and async function since we’ll use the promise version of Supertest.

Then using Nock, we mock the response as follows:

As we can see, the mock request is a GET request, which is the same as what we had in app.js. Also, the URL is the same as what we had there.

The difference is that we return a 200 response with the mockResponse object in the test.

Now it doesn’t matter what the actual API returns. Nock will intercept the request and always return the content of mockResponse as the response body for the request made in our route in app.js.

Then we just have to call the route with Supertest as follows:

const res = await request(app).get('/');

To call the route in app.js and then check the response as follows:

expect(res.status).toBe(200);  
expect(res.body).toEqual(mockResponse);

The toEqual method checks for deep equality so we can pass in the whole object to check.

Photo by Filip Mroz on Unsplash.


Running the Test

Now to run our test, we have to add:

"test": "jest --forceExit"

To the scripts section of package.json.

We need the --forceExit option so that the test will exit once the test is run. This is a bug that’s yet to be resolved.

Now we can run the test by running:

npm test

Then we should get:

We should get the same thing no matter how many times we run the test since we mocked the response of the API.

The real API returns something different every time we make a request to it, so we can’t use it for our tests.

Even if we could, it’d be much slower and the API might not always be available.


Conclusion

With Nock, we can mock responses easily for external API requests in our app. Then we can focus on just running our app’s code and checking the results.

Now we have tests that run fast and produce repeatable results that don’t depend on anything external to the tests.

Categories
Testing

Jasmine — Timer, and Async Tests

Testing is an important part of JavaScript.

In this article, we’ll look at how to create more complex tests with Jasmine.

Testing JavaScript Timeout Functions

We can use Jasmine to test JavaScript timeout functions.

We can use the jasmine.clock() method to do this.

For instance, we can write:

describe("Manually ticking the Jasmine Clock", function () {
  let timerCallback;

  beforeEach(function () {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it("causes a timeout to be called synchronously", function () {
    setTimeout(function () {
      timerCallback();
    }, 100);

    expect(timerCallback).not.toHaveBeenCalled();
    jasmine.clock().tick(101);
    expect(timerCallback).toHaveBeenCalled();
  });
});

to create a timerCallback spy which we can watch.

We call jasmine.clock().install() to create the Jasmine timer.

And we call jasmine.clock().uninstall() to remove it at the end.

Then we call tick to change the time to what we want.

This way, we can check is our setTimeout callback is called.

Also, we can use it to test setInterval .

For instance, we can write:

describe("Manually ticking the Jasmine Clock", function () {
  let timerCallback;

  beforeEach(function () {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it("causes a timeout to be called synchronously", function () {
    setInterval(function () {
      timerCallback();
    }, 100);

    expect(timerCallback).not.toHaveBeenCalled();

    jasmine.clock().tick(101);
    expect(timerCallback.calls.count()).toEqual(1);

    jasmine.clock().tick(102);
    expect(timerCallback.calls.count()).toEqual(2);
  });
});

Like with the setTimeout test, we change the clock to the time we want with the tick method.

The time is changed relative to the time that the last tick is called.

Mocking the Date

We can use the mockDate method to mock the date we want.

For instance, we can write:

describe("Manually ticking the Jasmine Clock", function () {
  let timerCallback;

  beforeEach(function () {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it("mocks time time", function () {
    const baseTime = new Date(2020, 0, 1);

    jasmine.clock().mockDate(baseTime);

    jasmine.clock().tick(50);
    expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
  });
});

We et the baseTime to a Date instance and then use that with mockDate to set the date to when we want.

Then we call tick to move the time.

And then we used getTime to get the current time after tick is called.

Async Support

Jasmine supports testing async code.

We can test async code with:

describe("Using callbacks", function () {
  beforeEach(function (done) {
    setTimeout(function () {
      value = 0;
      done();
    }, 1);
  });

  it("supports sequential execution of async code", function (done) {
    value++;
    expect(value).toBeGreaterThan(0);
    done();
  });
});

We have the beforeEach callback that has a setTimeout function call.

We call done in the callback so that the test code is run.

In the test code, the callback takes the done parameter to let us call done to indicate that the test is done.

To fail a test, we can use done.fail to fail the test.

For instance, we can write:

describe("Using callbacks", function () {
  var foo = function (x, callBack1, callBack2) {
    if (x) {
      setTimeout(callBack1, 0);
    } else {
      setTimeout(callBack2, 0);
    }
  };

  it("should not call the second callBack", function (done) {
    foo(true,
      done,
      function () {
        done.fail("Second callback has been called");
      }
    );
  });
});

We called done.fail in the 2nd callback so that if it’s run, the test will fail.

Conclusion

We can run a variety of tests that has timers and async code with Jasmine.

Categories
Testing

Jasmine — Test Array, Strings, and Time-Dependent Code

Testing is an important part of JavaScript.

In this article, we’ll look at how to create more complex tests with Jasmine.

Array Checks

We can use the jasmine.arrayContaining method to check the content of the array.

For example, we can write:

describe("jasmine.arrayContaining", function () {
  let foo;

  beforeEach(function () {
    foo = [1, 2, 3];
  });

  it("matches arrays with some of the values", function () {
    expect(foo).toEqual(jasmine.arrayContaining([3, 1]));
    expect(foo).not.toEqual(jasmine.arrayContaining([6]));
  });

  describe("when used with a spy", function () {
    it("is useful when comparing arguments", function () {
      const callback = jasmine.createSpy('callback');
      callback([1, 2, 3, 4]);
      expect(callback)
        .toHaveBeenCalledWith(
          jasmine.arrayContaining([4, 2, 3])
        );
      expect(callback)
        .not
        .toHaveBeenCalledWith(
          jasmine.arrayContaining([5, 2])
        );
    });
  });
});

We have the jasmine.arrayContaining method to check for the numbers in the array.

It returns true if the array being checked has all the items in the array we passed into arrayContaining .

We can do the same with function arguments.

String Matches

We can use the jasmine.stringMatching to check if a string has a given substring or pattern.

For instance, we can write:

describe('jasmine.stringMatching', function () {
  it("matches as a regex", function () {
    expect({ foo: 'baz' })
      .toEqual({
        foo: jasmine.stringMatching(/^baz$/)
      });
    expect({ foo: 'foobarbaz' })
      .toEqual({
        foo: jasmine.stringMatching('bar')
      });
  });

  describe("when used with a spy", function () {
    it("is useful for comparing arguments", function () {
      const callback = jasmine.createSpy('callback');
      callback('foobarbaz');
      expect(callback)
        .toHaveBeenCalledWith(
          jasmine.stringMatching('baz')
        );
      expect(callback)
        .not
        .toHaveBeenCalledWith(
          jasmine.stringMatching(/^baz$/)
        );
    });
  });
});

We have the jasmine.stringMatching method with a regex and a string.

It lets us check for a regex pattern and a substring.

Asymmetric Equality Tester

We can create our own equality tester to do our check.

For instance, we can write:

describe("custom asymmetry", function () {
  const tester = {
    asymmetricMatch(actual) {
      return actual.includes('bar');
    }
  };

  it("dives in deep", function () {
    expect("foo,bar,baz,quux").toEqual(tester);
  });

  describe("when used with a spy", function () {
    it("is useful for comparing arguments", function () {
      const callback = jasmine.createSpy('callback');
      callback('foo,bar,baz');
      expect(callback).toHaveBeenCalledWith(tester);
    });
  });
});

We created our own tester object with the asymmericMatch method to check for what we want.

We just return a boolean expression with the condition we want to check.

Then we can use it with the toHaveBeenCalledWith or toEqual .

Therefore, we can check for the values and the arguments.

Jasmine Clock

We can test time-dependent code with the jasmine.clock method.

For example, we can write:

describe("custom asymmetry", function () {
  let timerCallback;

  beforeEach(function () {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it("causes a timeout to be called synchronously", function () {
    setTimeout(function () {
      timerCallback();
    }, 100);
    expect(timerCallback).not.toHaveBeenCalled();
    jasmine.clock().tick(101);
    expect(timerCallback).toHaveBeenCalled();
  });
});

We have the jasmine.clock().install() to make Jasmine mock the time.

Then we call jasmine.clock().uninstall() to remove the Jasmine clock.

In our test, we can change the time to what we want.

And then we can do our checks after some amount of time is elapsed.

Conclusion

We can test time-dependent code with Jasmine’s clock methods.

Also, we can check for array and string items with built-in functions.

And we can make our own matches to test what we want.