Logo

0x5a.live

for different kinds of informations and explorations.

GitHub - duluca/angular-unit-test-helper: Helper functions to help write unit tests in Angular using mocks and spies

Helper functions to help write unit tests in Angular using mocks and spies - duluca/angular-unit-test-helper

Visit SiteGitHub - duluca/angular-unit-test-helper: Helper functions to help write unit tests in Angular using mocks and spies

GitHub - duluca/angular-unit-test-helper: Helper functions to help write unit tests in Angular using mocks and spies

Helper functions to help write unit tests in Angular using mocks and spies - duluca/angular-unit-test-helper

Powered by 0x5a.live 💗

Angular Unit Test Helper

Helper functions to help write unit tests in Angular using mocks and spies

Angular Version CircleCI Coverage Status

npm npm

Breaking Changes

  • createComponentMock has been removed, in favor of ng-mocks. See below for more info.

Goal

Minimal feature set to bridge obvious gaps in jasmine's support of modern JavaScript features. Ideally, I'd like to be using an auto-mocking library, but they don't play well with Angular's TestBed and RxJS\BehaviorSubject.

Here's a great article about auto-mocking libraries: https://hackernoon.com/with-typescript-3-and-substitute-js-you-are-already-missing-out-when-mocking-or-faking-a3b3240c4607.

Install

Add the package to your Angular project with npm:

npm i -D angular-unit-test-helper

Example Projects

Check out my sample projects that leverage angular-unit-test-helper:

Use the ng-tester package to generate robust and efficient unit tests using angular-unit-test-helper.

Usage

npm i -D ng-tester
npx ng g ng-tester:unit

For more information see https://github.com/bjsawyer/ng-tester/.

Features

autoSpyObj(classUnderTest: Function, spyProperties: string[] = [], observableStrategy = ObservablePropertyStrategy.Observable)

An extension of jasmine.createSpyObj with automatic discovery of functions and property getters given a Class, without requiring an instance of an object.

If you'd want to spy on a property without a getter, then you can simply pass in the property name like autoSpyObj(WeatherService, ['currentWeather$']).

Return value of autoSpyObj will be a true mock of the Class with spy-able methods and properties, making it easy to control and modify the return values of external dependencies during testing.

If property name ends with $ indicating that the property is an Observable, then you can specify an optional ObservablePropertyStrategy to prefer {}, new Observable() or new BehaviorSubject(null) default values for your mocked properties.

Usage

const weatherServiceSpy = autoSpyObj(WeatherService)

Alternate Usage

const weatherServiceSpy = autoSpyObj(
  WeatherService,
  ['currentWeather$'],
  ObservablePropertyStrategy.BehaviorSubject
)

autoSpyObj replaces the more verbose and difficult to maintain code, shown below:

jasmine.createSpyObj(WeatherService.name, [
  'getCurrentWeather',
  'getCurrentWeatherByCoords',
  'updateCurrentWeather',
])
addPropertyAsBehaviorSubject(weatherServiceSpy, 'currentWeather$')

addProperty(object: object, propertyName: string, valueToReturn: object)

When creating a mock object, add a property to that object with a property getter, so you can use a jasmine.spyOnProperty.

Usage

  weatherServiceMock = jasmine.createSpyObj('WeatherService', ['getCurrentWeather'])
  addPropertyAsBehaviorSubject(weatherServiceMock, 'currentWeather', null)
  ...
  spyOnProperty(weatherServiceMock, 'currentWeather$').and.returnValue({ temp = 72})

addPropertyAsBehaviorSubject(object: object, propertyName: string)

Convenience method to configure a property as a BehaviorSubject, so you can update its value before each test by calling .next on it.

Usage

  weatherServiceMock = jasmine.createSpyObj('WeatherService', ['getCurrentWeather'])
  addPropertyAsBehaviorSubject(weatherServiceMock, 'currentWeather$')
  ...
  weatherServiceMock.currentWeather$.next(fakeWeather)

deprecated createComponentMock(className: string, selectorName?: string, template = '')

Deprecated Use ng-mocks instead. https://ng-mocks.sudo.eu/api/MockComponent.

  1. Install using install i -D ng-mocks
  2. Refactor createComponentMock function calls to look like the sample below:
TestBed.configureTestingModule({
  declarations: [
    // for a single component
    MockComponent(Component),

    // for a set of components
    ...MockComponents(Component1, Component2),
  ],
})

Creates a mock class decorated with @Component, if not specified selector is inferred to be MyClassComponent -> app. Provides an option to override empty template.

Usage

TestBed.configureTestingModule({
      declarations: [ ..., createComponentMock('CurrentWeatherComponent')]
      ...
})

Note: Inferred selector in the above example is 'app-current-weather'.

Replaces boilerplate

@Component({
  selector: 'app-current-weather',
  template: '',
})
class MockCurrentWeatherComponent {}

getNativeElementByTestId<TComponent>(fixture: ComponentFixture<TComponent>, testId: string = '')

Helper function to retrieve the native element associated with a specific `data-testid`` within a component fixture.

Usage

<span class="left-pad" data-testid="title">LemonMart</span>
it('should render title', () => {
  const fixture = TestBed.createComponent(AppComponent)
  fixture.detectChanges()
  const titleElement = getNativeElementByTestId(fixture, 'title')
  expect(titleElement.textContent).toContain('LemonMart')
})

injectClass<TDependency>(dependency: Type<TDependency> | AbstractType<TDependency>): TDependency

Helper function to inject a dependency, like a service, into the TestBed with a typed return variable.

Usage

beforeEach(() => {
  weatherService = injectClass(WeatherService)
})

Replaces

beforeEach(() => {
  weatherService = TestBed.inject(WeatherService)
})

injectSpy<TDependency>(dependency: Type<TDependency> | AbstractType<TDependency>): jasmine.SpyObj<TDependency>

Similar to injectClass, but more descriptive to read for developers if returning a mocked SpyObj.

Usage

beforeEach(() => {
  weatherServiceMock = injectSpy(WeatherService)
})

Replaces

beforeEach(() => {
  weatherServiceMock = TestBed.inject(WeatherService) as any
})

getAllFunctions(prototype: any, props?: (string | number | symbol)[])

Helper function that return all functions in a given Class using reflection, so you don't have to provide an instance of the object.

getAllProperties(prototype: any, props?: (string | number | symbol)[])

Helper function that return all property getters in a given Class using reflection, so you don't have to provide an instance of the object.

Contributing

  • Send PR, will accept
  • To setup the project, run npm install
  • Test against the example project listed below using npm pack to create a .tgz file and install the .tgz file using npm install -D ../path/to/your.tgz
    • Using npm link doesn't work as expected due devDependencies being symlinked to the parent Angular project, causing issues with the framework.
  • To publish the project, run npm version major|minor|patch

    Read more about that setup by Isaac Schlueter here

Angular Resources

are all listed below.

Resources

listed to get explored on!!

Made with ❤️

to provide different kinds of informations and resources.