Angular Testing Tips: FirstValueFrom

Write Clearer and More Concise Tests With Angular and RxJS

Bobby Galli
JavaScript in Plain English

--

Angular Testing Tips: FirstValueFrom (📷 bobbyg603)

Flexing 💪

Unit Testing is a lot like going to the gym, challenging and time-consuming, but ultimately leads to improved performance and a more robust system. Just as regular exercise helps to build strength and endurance, unit testing helps to identify and fix weaknesses in code, leading to more reliable software that better meets the needs of users and stakeholders. Testing might not be the most glamorous aspect of software development, but it is critical to building lasting systems and facilitating collaboration.

A reference project that demonstrates how to use firstValueFrom is available here:

The Problem 🤔

According to the RxJS docs, an observable is “a lazily evaluated computation that can synchronously or asynchronously return zero to (potentially) infinite values from the time it's invoked onwards”. To obtain values emitted by an observable, we call subscribe with a callback function. The callback function passed to subscribe is invoked each time the observable emits.

Determining if observable will emit synchronously or asynchronously is not obvious, therefore, the following test can run and pass without verifying the expectation:

it('#getObservableValue should return value from observable', () => {
service.getObservableValue().subscribe(value => {
expect(value).toBe('observable value');
});
});

The implementation of service.getObservableValue could be either of the following:

// Using Angular's HttpClient
getObservableValue(): Observable<string> {
return this.httpClient.get('https://some-url.com');
}

// Using a third-party library based on fetch
getObservableValue(): Observable<string> {
return from(this.someThirdPartyLibrary.fetch('https://some-url.com'));
}

In the latter case, someThirdPartyLibrary.fetch returns a promise. The from function converts a promise into an observable, making the value easier to use inside of RxJS operators. In a test, a mock of an HttpClient implementation and fetch implementation would resemble the following snippets (respectively):

// Mock implementation of Angular's HttpClient
const httpClient = jasmine.createSpyObj('HttpClient', ['get']);
httpClient.get.and.returnValue(of('observable value'));

// Mock implementation of a third-party library based on fetch
const someThirdPartyLibrary = jasmine.createSpyObj('ThirdPartyLib', ['fetch']);
someThirdPartyLibrary.fetch.and.resolveTo('observable value');

When mocking HttpClient a value is returned using the RxJS of function which emits a value synchronously. In the mock implementation of the third-party library, resolveTo is used and resolves a value asynchronously.

A test that calls subscribe will execute the subscribe callback synchronously in the HttpClient case, but not the fetch case and jasmine will output a console error that the test has no expectations.

Not good!

The Old Solution 👴

If you’ve read Angular testing documentation, you might recognize the following snippet:

it('#getObservableValue should return value from observable', (done: DoneFn) => {
service.getObservableValue().subscribe(value => {
expect(value).toBe('observable value');
done();
});
});

The example above invokes the Jasmine done callback to mark the test as completed. Calling subscribe and explicitly marking a test as done works in simple circumstances, but there are a few drawbacks.

  1. Every developer writing tests has to remember to call done
  2. The done call requires an extra line of code takes up vertical screen real estate
  3. Calling subscribe nests expectations and breaks Arrange, Act, Assert

Additionally, when testing observable sequences, performing expectations on anything other than the first emitted value requires messy workarounds.

A Newer Solution 👶

Asynchronous functions allow us simplifying testing by writing code without callbacks. Converting a value emitted by an observable to a promise allows developers to leverage the await keyword to ensure that values are obtained and expectations are performed in the order they were written.

Prior to RxJS 7.0, developers could call toPromise on an observable. As of version 7.0 RxJS has released 2 new helper functions lastValueFrom and firstValueFrom. We can use await and firstValueFrom to convert an observable to a promise and await a value without calling subscribe.

We can simplify the test from the Angular docs by using async/await:

it('#getObservableValue should return value from observable', async () => {
const value = await firstValueFrom(service.getObservableValue());
expect(value).toBe('observable value');
});

The test can be simplified further by leveraging expectAsync:

it('#getObservableValue should return value from observable', async () => 
expectAsync(firstValueFrom(service.getObservableValue()))
.toBeResolvedTo('observable value')
);

The example above simplifies the test to a single expression, returning the result of expectAsync via an arrow function expression. Tests that don’t use an arrow function expression will need to return or await expectAsync explicitly.

Outputs

Converting observables to promises allows for some neat tricks when testing Angular Output properties. For instance, we can create a promise and hang on to its reference in order to verify a value that’s emitted at a later time.

@Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.scss']
})
export class CounterComponent {
@Output() countChange = new EventEmitter<number>();

count = 0;

onCountChange(newCount: number): void {
this.count = newCount;
this.countChange.emit(newCount);
}
}

A test for onCountChange would look something like this:

it('should emit updated count', async () => {
const newCount = 3;
const resultPromise = firstValueFrom(component.countChange);

component.onCountChange(newCount);
const result = await resultPromise;

expect(result).toEqual(newCount);
});

Under the hood, firstValueFrom creates a subscription, and converts the observable to a promise that will resolve when the first value is emitted. We save the promise to the resultPromise variable, and get result by calling onCountChange and awaiting resultPromise.

Skip

The skip operator will skip a given number of emissions and can be used alongside firstValueFrom to verify a sequence of values is emitted by an observable. When paired with firstValueFrom we can run expectations against any value emitted by an observable.

it('should emit count each time onCountChange is called', async () => {
const values = [1, 2];
const firstResultPromise = firstValueFrom(component.countChange);
const secondResultPromise = firstValueFrom(component.countChange.pipe(skip(1)));

values.forEach(value => component.onCountChange(value));
const firstResult = await firstResultPromise;
const secondResult = await secondResultPromise;

expect(firstResult).toEqual(values[0]);
expect(secondResult).toEqual(values[1]);
});

Take and ToArray

Instead of using skip to test each emission separately, we can use the take and toArray operators to ensure an observable emits a series of values.

it('should emit count each time onCountChange is called', async () => {
const values = [1, 2, 3];
const resultPromise = firstValueFrom(
component.countChange
.pipe(
take(values.length),
toArray()
)
);

values.forEach(value => component.onCountChange(value));
const result = await resultPromise;

expect(result).toEqual(jasmine.arrayContaining(values));
});

The take operator allows us to keep observable pipe’s inner subscription active until countChange has emitted 3 values. Once 3 values have been emitted, the subscription completes, and toArray aggregates all the emitted values into an array.

Tick

When using the delay or interval operators, tests must simulate the passage of time in order to trick observables into emitting results. This next example uses the delay operator to simulate a slow network call in order to demonstrate a loading spinner.

@Component({
selector: 'app-affirmations',
templateUrl: './affirmations.component.html',
styleUrls: ['./affirmations.component.scss']
})
export class AffirmationsComponent {
affirmation$: Observable<string>;
loading$: Observable<boolean>;
private affirmationSubject: Subject<void>;

constructor(private affirmationService: AffirmationsService) {
this.affirmationSubject = new BehaviorSubject<void>(null as any);

this.affirmation$ = this.affirmationSubject
.pipe(
switchMap(() => this.affirmationService.getAffirmation()),
delay(1000), // Simulate a slow network call
startWith('Loading...'),
share(),
);

const startLoading$ = this.affirmationSubject.pipe(map(() => true));
const stopLoading = this.affirmation$.pipe(map(() => false));
this.loading$ = merge(startLoading$, stopLoading);
}

onClick(): void {
this.affirmationSubject.next();
}
}

To test that the correct value is emitted without delaying test execution, the fakeAsync zone and tick function are used to simulate the passage of time.

The following example stores a promise reference, simulates the passage of time, and ultimately verifies the correct result.

it('should return an affirmation', fakeAsync(async () => {
const resultPromise = firstValueFrom(component.affirmation$.pipe(skip(1)));

tick(1000);
const result = await resultPromise;

expect(result).toEqual(affirmation);
}));

The value from startWith is skipped via the skip opperator. After calling tick the result promise resolves near-instantly and we can await a value from resultPromise. This test is wrapped in the fakeAsync zone so that tick can simulate the passage of time.

Further Investigation 🕵️

RxJS Marble Diagrams are another solution for testing observable sequences. Marble tests allow you to visually declare and run expectations against observable sequences. You can find an excellent explanation of how write marble tests here.

Conclusion 👏

This article discussed using the RxJS firstValueFrom helper to convert observables into promises. By converting observables into promises, we wrote tests that are clear, concise, and flexible. We tested component Outputs, and explored how we can use firstValueFrom with the skip, take, and toArray operator functions to test observable sequences. Finally, we explorered the fakeAsync zone, saving promise references to variables, and using tick to simulate the passage of time.

Thanks for reading!

Want to Connect?

If you found the information in this tutorial useful please subscribe on Medium, follow me on Twitter, and/or subscribe to my YouTube channel.

More content at PlainEnglish.io.

Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.

Interested in scaling your software startup? Check out Circuit.

--

--

Software developer at BugSplat. Big friendly giant, golf ball whacker guy, goofball, lover of tacos.