# JavaScript Testing

* [Noun](/10x-developer/javascript/javascript_testing.md#noun)
* [Jasmine Cheatsheet](/10x-developer/javascript/javascript_testing.md#jasmine-cheatsheet)
* [Jasmine + Angular](/10x-developer/javascript/javascript_testing.md#jasmine--angular)
* [Read Istanbul Report](/10x-developer/javascript/javascript_testing.md#read-istanbul-report)

## Noun

Unit Testing

* test runner
  * Karma. It runs test suite written in Jasmine, Mocha etc.
  * cucumber
* test framework: `beforeEach, describe, context, it`
  * Jasmine
  * Mocha
* assertion library: everything inside the it block: `expect, equal, and exist`
  * Chai:&#x20;
  * power-assert "No API is the best API"

Popular tools:

* Jest = test framework + test runner

End to End Testing

* end to end testing framework: Protractor (run against real browser)

## Jasmine Cheatsheet

### Spy (for methods, can track calls or specify return value)

```javascript
// Spy on existing method
beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      }
    };

    // Spy method
    spyOn(foo, 'setBar');

    // Return specified value
    spyOn(foo, "getBar").and.returnValue(745);

    // Throw error
    spyOn(foo, "setBar").and.throwError("404");
});

// Use createSpy to stub a method
beforeEach(function() {
    foo = {
      setBar: jasmine.createSpy('setBar')
    };

    // Test
    expect(foo.setBar).toHaveBeenCalled();
});

// Use createSpy to create bare method
beforeEach(function() {
    let foo = jasmine.createSpy('foo');

    // Test
    foo('you can pass any', 'args');
    expect(foo).toHaveBeenCalledWith('you can pass any', 'args');
});

// Use createSpyObj to create an object containing multiple methods
// Useful for mocking Angular service
beforeEach(function() {
    let $window = jasmine.createSpyObj<angular.IWindowService>('$window', ['open', 'alert']);
    (<jasmine.Spy>$window.alert).and.returnValue({});

    // Test
    $window.open();
    expect($window.open).toHaveBeenCalled();
});

// Use spy to create an object containing both methods and property
// You cannot use createSpyObj as it's for methods only
beforeEach(function() {
    foo = {
      bar: {},
      setBar: jasmine.createSpy('setBar')
    };

    // Test
    foo.setBar();
    expect(foo.bar).toEqual({});
    expect(foo.setBar).toHaveBeenCalled();
});
```

### Matcher

```javascript
// Match with type
expect({}).toEqual(jasmine.any(Object));
expect(123).toEqual(jasmine.any(Number));

// Match function, useful to test callback function
expect(service.registerCallback).toHaveBeenCalledWith('key', jasmine.any(Function));

// Match partial object
expect(foo).toEqual(jasmine.objectContaining({
  bar: "baz",
  fooFunc: jasmine.any(Function)
}));
```

### Tips

* Remember to call `$scope.$apply();` when testing promise. [Why?](http://davideguida.altervista.org/the-importance-of-scope-apply-when-testing-promises/)
* Call `done()` to instruct the spec is done for Asynchronous tests.
* Call the following methods when you testing $httpBackend. See [doc](https://docs.angularjs.org/api/ngMock/service/$httpBackend) and [source code](https://github.com/angular/angular.js/blob/master/src/ngMock/angular-mocks.js#L1860)

  ```typescript
    afterEach(()=> {
      $httpBackend.verifyNoOutstandingRequest();
      (<any>$httpBackend).verifyNoOutstandingExpectation(false);
    });
  ```
* Call $httpBackend.flush() or $scope.$digest(), but not both
* Any spec that calls a promise must execute expectations within a then or catch block (depending if the promise will resolve or reject)

## Jasmine + Angular 1.x

### Test component:

```javascript
describe('My Tests', () => {
  let $scope: angular.IScope,
    $q: angular.IQService
    $componentController: angular.IComponentControllerService;

  beforeEach(() => {
    angular.mock.module('module name');

    inject(($injector : angular.auto.IInjectorService)=> {
      $scope = $injector.get<angular.IRootScopeService>('$rootScope');
      $q = $injector.get<angular.IQService>('$q');
      $componentController = $injector.get<angular.IComponentControllerService>('$componentController');
    });
  });

  it('should skip', () => {
    pending();
  });
});
```

### Test Service itself: mock http

```typescript
import {MyService} from './my-service';

import IHttpService = angular.IHttpService;
//import IQService = angular.IQService;
import IHttpBackendService = angular.IHttpBackendService;
//import IRootScopeService = angular.IRootScopeService;

describe('My Service', ()=> {

  let myService: MyService,
      $http: IHttpService,
      $httpBackend: IHttpBackendService,
      $q: IQService,
      $rootScope: IRootScopeService;

  beforeEach(()=> {
    angular.mock.module('myModule');

    inject(($injector: angular.auto.IInjectorService)=> {
      $q = $injector.get<angular.IQService>('$q');
      $http = $injector.get<angular.IHttpService>('$http');
      $httpBackend = $injector.get<angular.IHttpBackendService>('$httpBackend');
      $rootScope = $injector.get<angular.IRootScopeService>('$rootScope');
    });

    myService = new MyService($http);
  });

  it('Should make HTTP call', (done)=> {
    $httpBackend.expectGET(`/URL/URL`).respond(200, 'fake data');

    myService.getData()
      .then(done);
    $httpBackend.flush();
  });
});
```

### Test service in component:

```typescript
let myService = jasmine.createSpyObj('myService', ['getData']);
(<jasmine.Spy>myService.getData).and.returnValue($q.when({}));
```

### How to use mock data?

```javascript
  beforeEach(() => {
    angular.mock.module('mock-jasmine/feature/mock-data.json');

    inject(($injector: angular.auto.IInjectorService) => {
      mockDataPerson = $injector.get('mockJasmineFeatureMockData');
    });
  });



  // mock $language return value
  spyOn($language, 'get').and.returnValue(na);
```

### Mistakes to avoid:

* Always to remember to return a promise!
* In test, always do a $scope.$digest() after calling a promise so that it triggers the promise chain.

## Read Istanbul Report

* <http://stackoverflow.com/a/36697606>
* <https://github.com/dwyl/learn-istanbul>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://zurassic.gitbook.io/10x-developer/javascript/javascript_testing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
