Unit Testing

Aspects of Front-End Testing

  • UI events
  • HTML rendering
  • Client - Server communication
  • Client side computations

Tools you'll need

  • Unit Testing Framework
    •  Jest, Mocha, Jasmine
  • Mock framework (Optional)
    • Sinon
  • Test runner (Optional)
    • Karma

Setting up Jasmine

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Testing with Jasmine</title>

    <!-- include jasmine -->
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.min.css"
    />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.min.js"></script>
    
    <!-- include jquery -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <!-- jasmine-jquery  -->
    <script src="./jasmine-jquery.js"></script>
    </head>
  <body>
    
    <!-- include the file you want to test  -->
    <script src="./index.js"></script>
    <!-- include the file in which you write your tests  -->
    <script src="./smile.spec.js"></script>
  </body>
</html>

Setting up Jasmine

* Note: If you want access to your html in jasmine,

put this at the top of your test js file:

// tell jasmine-jquery where to load html from
jasmine.getFixtures().fixturesPath = './';

// load html file
loadFixtures("index.html");

Basic Structure of Jasmine Tests


describe("I'm a set of related tests", function() {
	
	it("should do A", function () {
		...
	})

	it("should do B", function () {
		....
	})
})

* Use `describe()` to create a set of tests(spec)

* Use `it()` to write a single unit test

Test #1: createSmilePost()

* createSmilePost takes in data for a smile post:

* Returns an html element with the new smile post

{
    id: 9,
    rating: 9,
    date: "the data of the post",
    title: "title goes here...",
    content: "content of the post goes here...",
    likes: 2
  }
....

// smile data -> smile html element
const smileMarkup = ({ id, rating, date, title, content, likes }) => `
    <div data-id="d-${id}" class="smile-post">
        <div class="rating">
        ${rating}
        </div>
        <div class="main-content">
            <h3 class="title">
                ${title}
            </h3>
            <p>
                ${content}
            </p>
        </div>
        <div class="like-container">
            <button class="like"> </button>
            <div>${likes}</div>
        </div>
        <div class="date-container">
            <div class="date">
                ${date}
            </div>
        </div>
    </div>
`;

const createSmilePost = (smileData) => strToHtml(smileMarkup(smileData))
...

Test #1: createSmilePost()

describe("testing createSmilePost", () => {
  it("sets data-id properly", () => {
    const smile = {
      id: 1,
      rating: 3,
      date: "date",
      title: "Smile Please",
      likes: 2,
      content: `content`
    };

    const smilePost = createSmilePost(smile);
    expect(smilePost.dataset.id).toBe(`d-${1}`);
  });
});

Test #1: createSmilePost()

Test #2: increment likes on click

* Lets test an event handler this time

 

* `renderPage()`

   * takes:

     * an html element

     * the entire smile data(an array of smile objects)

 

   * renders smile posts into the provided html element

 

   * adds event handlers to handle "liking" of a smile post

 

* Similar to RateProfessor.start()

Test #2: increment likes on click

const renderPage = (root, data) => {
  
  // create smiles
  root.appendChild(createSmiles(data));

  // add event handlers
  root.querySelectorAll(".smile-post").forEach(smile => {
    const likeBtn = getLikeButtonFromSmilePost(smile);
    likeBtn.addEventListener("click", () => {
      const likes = getLikeCountDiv(smile);
      const count = +likes.innerText;
      likes.innerText = count + 1;
    });
  });
};
describe("testing smile post likes onclick", () => {
    it("increments # likes on click", () => {
      // data for all smiles. We have only one smile post for our test
      const smiles = [{
        id: 1,
        rating: 3,
        date: "date",
        title: "Smile Please",
        likes: 2,
        content: `content`
      }];
      // create an html element for renderPage()
      const renderTarget = document.createElement("div");
      // call renderPage with our smiles data
      renderPage(renderTarget, smiles);
      
      // get all dom elements we need
      const smilePost = $(renderRoot).find(".smile-post[data-id='d-1']");
      const likeDiv = smilePost.find(".like-container>div");
      const likeBtn = smilePost.find(".like-container>button");
	
      // before click check if the like counter has the correct  value
      expect(+likeDiv.text()).toBe(2);
      // trigger a click on the like button
      likeBtn.trigger('click');
      // check if the like counter got incremented
      expect(+likeDiv.text()).toBe(3);
    });
  });

Test #2: increment on click

Open up your tests.html to check your tests

Some guidelines for unit testing

* One feature per test

 

* Keep tests simple

 

* Keep tests detereministic

 

Resources

* Jasmine

   https://jasmine.github.io/

 

* jasmine-jquery

   https://github.com/velesin/jasmine-jquery

 

* For React/Vue:

   * jest

      https://jestjs.io/

 

   * enzyme

      https://airbnb.io/enzyme/

Unit Testing

By Devjeet Roy

Unit Testing

Basics of front-end unit testing in Javascript

  • 645