Developing with a Stub backend on Angular 2

In Angular 1 I always use during development a stub or mock REST API backend that allows me running the application without the real API. In this way, the front-end implementation can be started before or in parallel with the backend. It is also useful for small prototypes or demos.

You could quickly implement a fake backend using Node or something similar, but I recommend to mock the backend directly in Angular. It is faster because the application won’t hit the wire and what I really like is that it is stateless (every time the browser is reloaded the stub data is reinitialized).

Those two points are great for local development but what I find fundamental is to use it for running protractor E2E tests, that way I always can rely on a backend substitute that does not keep state between test (protractor reloads the browser before every test, and again the stub data gets reinitialized).

This post shows how to configure a stub backend on Angular 2 using the MockBackend test class.
On develop mode we will override the Http provider to, instead of using a real backend implementation such as XHRBackend, use the MockBackend.

I define the stub backend provider in a separate file stub-backend-provider.ts, here is all the magic happens swapping the backend provider for develop mode and defining stub responses for determined HTTP requests.

src/app/stub-backed/stub-backend-provider.ts

import { Http, BaseRequestOptions, Response, ResponseOptions, RequestMethod, XHRBackend } from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';

import { environment } from '../../environments/environment';
import { Task } from '../tasks-list/task';
import { generateUuid, getUuidFromUrl } from './stub-backend-utils';

/**
 * Provider to allow the use of a stub backend instead of a real Http service for backend-less development.
 */
export let stubBackendProvider = {
  provide: Http,
  deps: [MockBackend, BaseRequestOptions, XHRBackend],
  useFactory: (mockBackend: MockBackend, options: BaseRequestOptions, realBackend: XHRBackend) => {

    if (!environment.stubBackend) {
      console.log('Configuring real Http backend...');
      return new Http(realBackend, options);
    }

    console.log('Configuring stub Http backend...');

    let tasks: Task[] = [
      {id: '9509c8b4-ad34-4378-b49c-c9206dfd7f75', name: 'Buy milk', done: false, userId: 'user-1'},
      {id: '1b35d8f8-9e80-4316-b3e3-135a8f81200f', name: 'Pay rent', done: true, userId: 'user-1'}];

    mockBackend.connections.subscribe((connection: MockConnection) => {

      // wrap in timeout to simulate server api call
      setTimeout(() => {

        // Get all tasks
        if (connection.request.method === RequestMethod.Get && connection.request.url.match('/tasks$')) {
          connection.mockRespond(new Response(new ResponseOptions({body: tasks.slice()})));
          return;
        }

        // Save task
        if (connection.request.method === RequestMethod.Post && connection.request.url.match('/tasks$')) {
          let newTask = JSON.parse(connection.request.getBody());
          newTask.id = generateUuid();
          tasks.push(newTask);

          connection.mockRespond(new Response(new ResponseOptions({body: newTask})));
          return;
        }

        // Delete task
        if (connection.request.method === RequestMethod.Delete && connection.request.url.match('/tasks/*')) {
          let id = getUuidFromUrl(connection.request.url);
          tasks = tasks.filter(task => task.id !== id);

          connection.mockRespond(new Response(new ResponseOptions()));
          return;
        }
      }, 500);

    });

    return new Http(mockBackend, options);
  }
};

The provider requires uses a new Angular CLI environment property that I called stubBackend, with a value of true on develop and false on production.
/src/environments/environment.ts

export const environment = {
  production: false,
  stubBackend: true
};

/src/environments/environment.prod.ts

export const environment = {
  production: false,
  stubBackend: false
};

Continue reading “Developing with a Stub backend on Angular 2”

Unit test exception messages using JUnit ExpectedException Rule

Traditionally JUnit supported expected exceptions using the @Test annotation like this

@Test(expected = ParsingException.class)
public void parseGame_when_fileIsEmpty_then_throwsParsingException() throws Exception {
   // Arrange
   BufferedReader bufferedReaderMock = mock(BufferedReader.class);
   when(bufferedReaderMock.readLine()).thenReturn("");

   // Act
   gameJsonParser.parseGame(bufferedReaderMock);
}

This has two inconveniences. The expectation should be defined outside the test body and it does not support asserting the exception message.

A workaround commonly used is handling the exception with a try/catch block and assert it manually.

@Test
public void parseGame_when_fileIsEmpty_then_throwsParsingException() throws Exception {
   // Arrange
   BufferedReader bufferedReaderMock = mock(BufferedReader.class);
   when(bufferedReaderMock.readLine()).thenReturn("");

   // Act
   try {
     gameJsonParser.parseGame(bufferedReaderMock);
     fail("Should have thrown ParsingException but did not!");
   }
   catch(ParsingException e) {
     String message = "Parsing failed. Empty game file.";
     assertEquals(message, e.getMessage());
   }
}

As you can see that brings lot of boilerplate code to the test.

Since JUnit 4.7 we can use the ExpectedException core rule to assert exceptions in a more natural flow and also to assert their messages.

ExpectedException: Allows a test to specify expected exception types and messages in the test itself.

@Rule
public ExpectedException exception = ExpectedException.none();

@Test
public void parseGame_when_fileIsEmpty_then_throwsParsingException() throws Exception {
   // Arrange
   BufferedReader bufferedReaderMock = mock(BufferedReader.class);
   when(bufferedReaderMock.readLine()).thenReturn("");

   exception.expect(ParsingException.class);
   exception.expectMessage("Parsing failed. Empty game file.");

   // Act
   gameJsonParser.parseGame(bufferedReaderMock);
}

References:
https://paucls.wordpress.com/2013/11/12/using-junit-rules
https://www.infoq.com/news/2009/07/junit-4.7-rules