basics of Unit testing

  • Why and why not write tests?
  • Types of automated tests
  • How to write good unit tests
  • Code coverage
  • UT Frameworks: Jasmine
  • Testing Angular apps

Why and why not write unit tests

some perks of writing UT

Better understanding of the requirements

More robust components

Avoid bugs created by a refactor

Feature docs for developers

Less buggy continuous delivery

types of automated tests

end to end (e2e)

Term “End to End testing is defined as a testing method which determines whether the performance of application is as per the requirement or not. It is performed from start to finish under real world scenarios like communication of the application with hardware, network, database and other applications.

End-to-end testing is a technique used to test whether the flow of an application right from start to finish is behaving as expected. The purpose of performing end-to-end testing is to identify system dependencies and to ensure that the data integrity is maintained between various system components and systems. 

Integration tests

Integration testing (sometimes called integration and testing, abbreviated I&T) is the phase in software testing in which individual software modules are combined and tested as a group.

Integration testing is a software testing methodology used to test individual software components or units of code to verify interaction between various software components and detect interface defects. Components are tested as a single group or organized in an iterative manner.

Unit tests

 Software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use.

Is a level of software testing where individual units/ components of a software are tested. The purpose is to validate that each unit of the software performs as designed.

Unit tests

Effort to write & run

Reliability

UT

IT

E2E

How to write good unit tests

The structure of a UT

  1. Set up conditions for the test
  2. Trigger the method
  3. Verify the relevant results
  4.  Undo modifications (clean up)

Some advice

Test you tests: Is it failing when it needs to fail?
Does it work consistently?

Name your tests correctly: It will help you focus on what you need to test.

One logical assertion per test: It will keep the tests simple enough.

Keep them unit: It's too tempting to avoid mocks, but it will make debugging harder when they broke.

Ensure good reports on fail

A good test fail report should give you information about:

  1. What were you testing?
  2. What should it do?
  3. What was the behaviour?
  4. What was the expected behaviour?
My function should return the result of adding the stringified numbers in the array, as a number FAILED
        Expected 1 to be 8.

1

2

3

4

Code coverage

what is code coverage?

 A measure used to describe the degree to which the source code of a program is executed when a particular test suite runs.

coverage is a lie

myFunction(stringsArray: string[]) {
    return stringsArray
    .map(numString => parseInt(numString, 10))
    .reduce((prev, current) => prev + current);
  }
it('is a non sense test', () => {
    const goodArray = ['1', '3', '2'];

    expect(this.service.myFunction(goodArray)).toBeDefined();
  });
it('is and incorrect test', () => {
    const badArray = ['1', '3', 'L'];

    expect(this.service.myFunction(badArray)).not.toBe(jasmine.any(String));
  });

coverage is useful

myFunction(stringsArray: string[]) {
    return stringsArray
    .map(numString => parseInt(numString, 10))
    .filter(shouldBeNum => typeof shouldBeNum === 'number' ? shouldBeNum : 0)
    .reduce((prev, current) => prev + current);
  }
it('should add all the numbers in the array', () => {
    const arrayToAdd = ['1', '3', '4'];

    expect(this.service.myFunction(arrayToAdd)).toBe(8);
  });

How much should i test?

There's no magic %, it's up to your project's needs.

In case of doubt, test it.

Ut Frameworks: Jasmine

why jasmine?

In this case, just because. Most UT frameworks are really similar, choose the one that fits you and your app.

  • https://jasmine.github.io/ -> Fast and simple
  • https://qunitjs.com/ -> Used by JQuery projects
  • http://mochajs.org/ -> Faster for async tests (mocha.parallel)
  • https://facebook.github.io/jest/ -> Easy layout testing

Basic testing

describe("Test suit name", function() {
  var a;

  it("should have at least one test", function() {
    a = true;

    expect(a).toBe(true);
    a = undefined;
  });


  it("can have more than one", function() {
    a = false;

    expect(a).toBe(false);
    a = undefined;
  });
});
  1. Set up
  2. Trigger
  3. Verify
  4.  Undo

Easier set up and undo

beforeEach and afterEach will run code before or after each test (it will run once per test).

beforeAll and afterAll will run code just once, before/after all the specs.

describe("Test suit name", function() {
  var a;

  afterEach(function() {
    a = undefined;
  })

  it("will set a to undefined after this test", function() {
    a = true;

    expect(a).toBe(true);
  });


  it("will ALSO set a to undefined after this", function() {
    a = false;

    expect(a).toBe(false);
  });
});

Easier set up and undo

this allows to share variables between beforeEach, afterEach and it.

It's reseted for each test.

describe("Test suit name", function() {

  beforeEach(function() {
    this.a = 'invalid'
  })

  it("should have the initial value", function() {
    expect(this.a).toBe('invalid');
    this.a = true;

    expect(this.a).toBe(true);
    this.b = false;
  });


  it("is reset between tests", function() {
    expect(this.b).toBe(undefined);
  });
});

Other Matchers

.not for negative assertions: expect(false).not.toBe(true)

.toEqual will compare the values of objects, but not their reference: expect(foo).toEqual(clonedFoo)

.toMatch for regular expresions: expect(message).toMatch(/bar/)

.toBeDefined,  .toBeUndefined .toBeNull  and .not.toBeNull are a fast way to pre-check if a variable or result is empty.

.toBeTruthy and .toBeFalsy for not boolean values:

expect(1).toBeTruthy();   expect(0).toBeFalsy()

.toThrow and .toThrowError for error management testing:

expect(foo).not.toThrow();  expect(foo).toThrow('F** you');   expect(foo).toThrowError('F** you')

spies

A spy will keep track of the calls made to a function, and can modify the result.

It only exists in the context where it's declared

describe("A spy", function() {
  var foo, bar = null;

  beforeEach(function() {
      foo = { setBar: function(value) { bar = value; }
    };

    spyOn(foo, 'setBar');

    foo.setBar(123);
    foo.setBar(456, 'another param');
  });

  it("tracks that the spy was called", function() {
    expect(foo.setBar).toHaveBeenCalled();
  });

  it("tracks that the spy was called x times", function() {
    expect(foo.setBar).toHaveBeenCalledTimes(2);
  });

  it("tracks all the arguments of its calls", function() {
    expect(foo.setBar).toHaveBeenCalledWith(123);
    expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
  });
});

spies.and

By default, a spy will prevent the execution of the function which is spying, but this behaviour can be modified with .and

.and.callThrough will execute the function

.and.returnValue will return what is declared, without executing the real function: spyOn(foo, 'getBar').and.returnValue('moked result');

.and.throwError: spyOn(foo, 'setBar').and.throwError('Test error');

.and.callFake will call a mock function:

spyOn(foo, "add").and.callFake(function(firstArg, secondArg) {
      return firstArg + secondArg;

});

testing angular apps

basic tests

Angular is based in classes, which makes unit testing really easy

// import the component, service, pipe, etc to test
import { LikeComponent } from './like.component';

describe('LikeComponent', () => {
   // declare a variable for the element to test
   let component: LikeComponent;

   beforeEach(() => {
    // create a new instance of the element each time
    // it's easier and safer the undo everything after every test
     component = new LikeComponent();
   });

    it('should toggle the iLike property when I click it', () => {
      component.iLike = true; // setup

      component.click(); // trigger

      expect(component.iLike).toBe(false); // verify
      // clean up is done when component is reasigned
    });
  // write as many test as you need
});

Testing components: the testbed

TestBed creates an isolated testing module

import { TestBed, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { VoterComponent } from './voter.component';

describe('VoterComponent', () => {
  let component: VoterComponent;
  let fixture: ComponentFixture<VoterComponent>;

    beforeEach(() => {
      TestBed.configureTestingModule({
        // Declare the component in the mocked module
        declarations: [ VoterComponent ]
      });
       
      // the fixture is a reference to the environment around the created component
      // it includes the DebugElement
      fixture = TestBed.createComponent(VoterComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
    });

    it('should hightliht the button if I have upvoted', () => {
      component.myVote = 1;
      // fixture.detectChanges fires our changes in the module
      // (updates dom, fires lifecycle hooks and events, etc)
      fixture.detectChanges(); 

      let de = fixture.debugElement.query(By.css('.glyphicon-menu-up'));

      expect(de.classes['highlighted']).toBeTruthy();

    });

});

mocking dependencies

Never use a real dependency in a UT

import { UsersComponent } from './users.component'; 
import { UserService } from './user.service'; 
import { Observable } from 'rxjs/Observable';

describe('UsersComponent', () => {
  let component: UsersComponent; 
  let service: UserService; 

  beforeEach(() => {
    service = new UserService(); // real instance of the dependency
    component = new UsersComponent(service); // use it to initiate the component
  });

  it('should set users property with the users retrieved from the server', () => {
    let users = [ 1, 2, 3 ];
    // but SPY every call to it
    spyOn(service, 'getUsers').and.returnValue(Observable.from([ users ]));

    component.ngOnInit();

    expect(component.users).toBe(users);
  });
});

This method can be painfull if the service would have had a lot of dependencies itself. Here is another option:

https://angular.io/guide/testing#test-a-component-with-a-dependency

Basics of unit testing

By Paqui Calabria

Basics of unit testing

  • 1,317