SOLID Principles for Testers: The Liskov Substitution Principle

It’s time to learn about the “L” in SOLID!  The Liskov Substitution Principle is named for Barbara Liskov, a computer scientist who introduced the concept in 1987.  The principle states that you should be able to replace objects in a superclass with objects of a subclass with no alterations in the program. 

In order to understand this, let’s use an example that is very familiar with testers: waiting for an element.  Here’s a class called WaitForElement that has two methods, waitForElementToBeVisible and waitForElementToBeClickable:

class WaitForElement {
  constructor() {}
  async waitForElementToBeVisible(locator) {
    await driver
      .wait(until.elementIsVisible
      (driver.findElement(locator)),10000)
  }
  async waitForElementToBeClickable(locator) {
    await driver
      .wait(until.elementIsEnabled
      (driver.findElement(locator)),10000)
  }
}

This class could be used to locate all kinds of elements.  Now imagine that the tester has created a class specifically for clicking on elements in dropdown lists, that extends the existing WaitForElement class:

class WaitForDropdownSelection extends WaitForElement {
  constructor() {
    super()
  }
  async waitForElementToBeClickable(locator) {
    let selection = await driver
    .wait(until.elementIsEnabled(driver.findElement(locator)),
      10000)
    selection.click()
  }
}

If we were going to use the WaitForElement class to select a city from a dropdown list of cities, it would look like this:

let waitForInstance = new WaitForElement()
waitForInstance.waitForElementToBeVisible
  (By.id(‘cities’)).click()
waitForInstance.waitForElementToBeClickable
  (By.id(‘New York’)).click()

But if we were going to use the WaitForDropdownSelection class to select a city instead, it would look like this:

let waitForDropdownInstance = new WaitForDropdownSelection()
waitForDropdownInstance.waitForElementToBeVisible
  (By.id(‘cities’)).click()
waitForDropdownInstance.waitForElementToBeClickable
  (By.id(‘New York’))

Do you see the difference?  When we use the waitForElementToBeClickable method in the WaitForDropdownSelection class, the method includes clicking on the element:

selection.click()

But when we use the waitForElementToBeClickable method in the WaitForElement class, the method does not include clicking.  This violates the Liskov Substitution Principle.

To fix the problem, we could update the waitForElementToBeClickable method in the WaitForDropdownSelection to not have the click() command, and then we could add a second method that would wait and click:

class WaitForDropdownSelection extends WaitForElement {
  constructor() {
    super()
  }
  async waitForElementToBeClickable(locator) {
    await driver
      .wait(until.elementIsEnabled
      (driver.findElement(locator)), 10000)
  }
  async waitForElementAndClick(locator) {
    let selection = await driver
      .wait(until.elementIsEnabled
      (driver.findElement(locator)), 10000)
    selection.click()
  }
}

We’ve now adjusted things so that the classes can be used interchangeably. 

With the WaitForElement class:

let waitForInstance = new WaitForElement()
waitForInstance.waitForElementToBeVisible
  (By.id(‘cities’)).click()
waitForInstance.waitForElementToBeClickable
  (By.id(‘New York’)).click()

With the WaitForDropdownSelection class:

let waitForDropdownInstance = new WaitForDropdownSelection()
waitForDropdownInstance.waitForElementToBeVisible
  (By.id(‘cities’)).click()
waitForDropdownInstance.waitForElementToBeClickable
  (By.id(‘New York’)).click()

Or we could use the new method in the WaitForDropdownSelection class instead:

let waitForDropdownInstance = new WaitForDropdownSelection()
waitForDropdownInstance.waitForElementToBeVisible
  (By.id(‘cities’)).click()
waitForDropdownInstance.waitForElementAndClick
  (By.id(‘New York’))

Using extended classes is a great way to avoid duplicating code while adding new functionality.  But make sure when you are extending a class that the methods are interchangeable.