Arrange Act Assert

This is an optional lesson that looks into what can we do about more complex and convoluted tests to improve readability.

Explanation of the last unit test

Let’s take a look at the unit test from the previous lesson:

it('should create canvas, append it in the element, get a 2d context and draw the square', () => {
  const contextMock = { rect: jasmine.createSpy('rect') };
  const canvasMock = { getContext: jasmine.createSpy('canvasMock') };
  canvasMock.getContext.and.returnValue(contextMock);
  const documentSpy = spyOn(document, 'createElement').and.returnValue(canvasMock);
  const element = { appendChild: jasmine.createSpy('appendChild') };

  drawSquare(element, 10);

  expect(documentSpy).toHaveBeenCalledTimes(1);
  expect(element.appendChild).toHaveBeenCalledOnceWith(canvasMock);
  expect(canvasMock.getContext).toHaveBeenCalledOnceWith('2d');
  expect(contextMock.rect).toHaveBeenCalledOnceWith(0, 0, 10, 10);
});

Even though some separation is done using spacing, a lot is going on, and it could be difficult to follow. Let’s see what it looks like using some comments:

it('should create canvas, append it in the element, get a 2d context and draw the square', () => {
  // setting up the mock environment and input
  const contextMock = { rect: jasmine.createSpy('rect') };
  const canvasMock = { getContext: jasmine.createSpy('canvasMock') };
  canvasMock.getContext.and.returnValue(contextMock);
  const documentSpy = spyOn(document, 'createElement').and.returnValue(canvasMock);
  const element = { appendChild: jasmine.createSpy('appendChild') };
  // running the function-under-test
  drawSquare(element, 10);
  // Asserting expectations
  expect(documentSpy).toHaveBeenCalledTimes(1);
  expect(element.appendChild).toHaveBeenCalledOnceWith(canvasMock);
  expect(canvasMock.getContext).toHaveBeenCalledOnceWith('2d');
  expect(contextMock.rect).toHaveBeenCalledOnceWith(0, 0, 10, 10);
});

All we did is add a bit of metadata about our tests. It helps, but it also adds more work for the reader and the author of the tests. It turns out there’s a convention used to convey the same thing in a shorter style.

A succinct message

Arrange-Act-Assert

  • Arrange: set up dependencies, input, and so on.
  • Act: perform the action.
  • Assert: make the assertions.

This is how it looks after it’s applied on the above spec:

it('should create canvas, append it in the element, get a 2d context and draw the square', () => {
  // Arrange
  const contextMock = { rect: jasmine.createSpy('rect') };
  const canvasMock = { getContext: jasmine.createSpy('canvasMock') };
  canvasMock.getContext.and.returnValue(contextMock);
  const documentSpy = spyOn(document, 'createElement').and.returnValue(canvasMock);
  const element = { appendChild: jasmine.createSpy('appendChild') };
  // Act
  drawSquare(element, 10);
  // Assert
  expect(documentSpy).toHaveBeenCalledTimes(1);
  expect(element.appendChild).toHaveBeenCalledOnceWith(canvasMock);
  expect(canvasMock.getContext).toHaveBeenCalledOnceWith('2d');
  expect(contextMock.rect).toHaveBeenCalledOnceWith(0, 0, 10, 10);
});

Is is redundant!?

Maybe it looks redundant and unnecessary at first. However, after getting used to it, we might find that looking at tests without it is hard. It’s like grammar for tests; it makes reading that much easier on the eye.

Optional

Using Arrange-Act-Assert is optional and just a recommendation. This isn’t the only test convention. We should find ours and stick with it.

Exercise

Go ahead and try to add Arrange-Act-Assert to one test case here. Use the code from the previous exercise:

Get hands-on with 1200+ tech skills courses.