DEV Community

loading...
This is Angular

Mocking with MSW and Nx

Jesús Rodríguez
Angular developer and author
Originally published at angular-tips.com Updated on ・6 min read

We are sitting in front of our new project and we need to start coding some new screens but the backend is just not ready yet. Isn't that familiar?

It is a good practice to have some sort of mocking mechanism so we can start coding as soon as we can and also make our unit testing less error prone by using well known data.

Yes, but that sounds overly complicated to achieve. We need to turn off and on the backend, swap modules around to enable or disable the mocking and be careful to not deploy any of that the production.

Well, not anymore.

Introducing MSW

MSW as they say, is the API mocking of the next generation. Right, but what does that mean? It works by intercepting requests on the network level or in other words, by using a service worker.

The interesting part is that it is invisible for us, developers. Isn't that great?

Creating our project using NX

There is no other reason of using NX other than it being awesome. Everything we are going to see here works with Angular CLI as well.

Let's create a new workspace with an Angular + Nest projects. The fact we are going to mock the backend doesn't mean we don't need a backend... eventually.

$ npx create-nx-workspace msw-tutorial
Enter fullscreen mode Exit fullscreen mode

When asked, select angular-nest and call the application whatever you want, I used spa. Then choose CSS (we are not going to do styles here) and No for the cloud.

Open the workspace in your favorite editor vscode and also run both the spa and the api:

$ npx nx serve
Enter fullscreen mode Exit fullscreen mode
$ npx nx serve api
Enter fullscreen mode Exit fullscreen mode

You need two terminal open for this.

Once it finish, we can navigate to http://localhost:4200 and then see:

Initial Application

That Message at the bottom is a message from our API. We can open apps/api/src/app/app.controller.ts if we want to take a look.

Setting up the environment for MSW

With our app working, let's set up MSW.

First, let's install it:

$ npm i -D msw
Enter fullscreen mode Exit fullscreen mode

MSW depends on a service worker being installed, so let's create it first:

$ npx msw init apps/spa/src
Enter fullscreen mode Exit fullscreen mode

This will copy the mockServiceWorker.js inside the spa project. You can copy it in a different place if needed, but for the sake of this tutorial, let's assume we installed it there. If asked to save the directory in the package.json, feel free to say no. We don't need it.

Our next step is register this mockServiceWorker.js within Angular. For that, open angular.json and update it:

"options": {
  "outputPath": "dist/apps/spa",
  "index": "apps/spa/src/index.html",
  "main": "apps/spa/src/main.ts",
  "polyfills": "apps/spa/src/polyfills.ts",
  "tsConfig": "apps/spa/tsconfig.app.json",
  "assets": [
    "apps/spa/src/favicon.ico",
    "apps/spa/src/assets",
    "apps/spa/src/mockServiceWorker.js"
  ],
  "styles": [
    "apps/spa/src/styles.css"
  ],
  "scripts": []
},
Enter fullscreen mode Exit fullscreen mode

Now when MSW ask for this service worker to be installed, Angular will be able to locale it.

Our next question is: When do we want to use mocking? Certainly not in production and sometimes at development. A common pattern is to create another environment called mock.

First, let's update again our angular.json to add a new configuration:

"development": {
  "buildOptimizer": false,
  "optimization": false,
  "vendorChunk": true,
  "extractLicenses": false,
  "sourceMap": true,
  "namedChunks": true
},
"mock": {
  "buildOptimizer": false,
  "optimization": false,
  "vendorChunk": true,
  "extractLicenses": false,
  "sourceMap": true,
  "namedChunks": true,
  "fileReplacements": [
    {
      "replace": "apps/spa/src/environments/environment.ts",
      "with": "apps/spa/src/environments/environment.mock.ts"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

It is a copy of development but adding a new environment.mock.ts file. So let's add it to apps/spa/src/environments:

File: environment.mock.ts

export const environment = {
  production: false,
};
Enter fullscreen mode Exit fullscreen mode

To make things easier, let's create a new script:

File: package.json

"scripts": {
    "ng": "nx",
    "postinstall": "node ./decorate-angular-cli.js && ngcc --properties es2015 browser module main",
    "nx": "nx",
    "start": "ng serve",
    "start-mock": "ng serve spa --configuration mock",
    "build": "ng build",
Enter fullscreen mode Exit fullscreen mode

To be able to serve the app with this new mock configuration, we have to add it to the angular.json:

"development": {
  "browserTarget": "spa:build:development"
},
"mock": {
  "browserTarget": "spa:build:mock"
}
Enter fullscreen mode Exit fullscreen mode

Creating our MSW configuration

Now with our environment set up, the next thing is create our actual mock, right? Since we are using NX, let's create a new library:

$ npx nx g @nrwl/workspace:library --name=mock-api --skipBabelrc --unitTestRunner=none
Enter fullscreen mode Exit fullscreen mode

Let's delete libs/mock-api/src/lib/mock-api.ts and create there:

File: handlers.ts

export const handlers = [];
Enter fullscreen mode Exit fullscreen mode

File: browser.ts

import { setupWorker } from 'msw';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);
Enter fullscreen mode Exit fullscreen mode

Also update libs/mock-api/src/index.ts:

export * from './lib/browser';
Enter fullscreen mode Exit fullscreen mode

At handlers we configure all the network calls we want to mock and at browser.ts we create a worker object that we can use to start MSW with our handlers.

Where should we start MSW? Since we only want to run it in mock mode, let's update apps/spa/src/environments/environments.mock.ts:

import { worker } from '@msw-tutorial/mock-api';

worker.start({
  onUnhandledRequest: 'bypass',
});

export const environment = {
  production: false,
};
Enter fullscreen mode Exit fullscreen mode

Here we made an important decision. What do we do with all those requests that are not handled by our mock? We bypass it to the real deal. By doing this, we can be selective with the mocks we want to have.

Now, we run our backend and frontend again:

$ npm run start-mock
Enter fullscreen mode Exit fullscreen mode
$ npx nx serve api
Enter fullscreen mode Exit fullscreen mode

Only that this time we are using our new start-mock script.

If we now open our site again at http://localhost:4200 we see, well, the exact same page:

Initial Application again

But if we open the console, we can see:

Console showing MSW working

MSW seems to be enabled and working. It is just that we haven't create a mock handler yet.

Before we move on, you may notice a warning in the console about one file that depends on 'debug'. If that is the case, open the angular.json and update it as follows:

"options": {
  "outputPath": "dist/apps/spa",
  "index": "apps/spa/src/index.html",
  "main": "apps/spa/src/main.ts",
  "polyfills": "apps/spa/src/polyfills.ts",
  "tsConfig": "apps/spa/tsconfig.app.json",
  "assets": [
    "apps/spa/src/favicon.ico",
    "apps/spa/src/assets",
    "apps/spa/src/mockServiceWorker.js"
  ],
  "allowedCommonJsDependencies": [
    "debug"
  ],
Enter fullscreen mode Exit fullscreen mode

In any case, let's create our first mock route. If we check our app.component we can see:

@Component({
  selector: 'msw-tutorial-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  hello$ = this.http.get<Message>('/api/hello');
  constructor(private http: HttpClient) {}
}
Enter fullscreen mode Exit fullscreen mode

We see, first, very bad practices by including a HTTP call here, but then we see that it is calling /api/hello.

Let's add a handler:

File: handlers.ts

import { rest } from 'msw';

export const handlers = [
  rest.get('/api/hello', async (req, res, ctx) => {
    return res(ctx.json({ message: 'Msw works like a charm!' }));
  }),
];
Enter fullscreen mode Exit fullscreen mode

This looks like pretty much like express.

If now we open our page again, we see:

Main application with mocking working

That is our mock!!

And if we check the console, we can see:

Console showing MSW details

This is just, perfect.

Go ahead and restart your app in development mode:

$ npx nx serve
Enter fullscreen mode Exit fullscreen mode

What do we see? No mock trace anywhere at all.

Conclusion

MSW is an easy way to add a mocking layer in an application. We can deliberately decide if we want to mock everything or just part of the application.

Once configured, we just need to add as many handlers as we need for our mocking purposes and we can go as complex as we need there. We can have a json "database" with fake data, or use faker for example.

But the best part is that it is completely invisible for Angular. We don't need to mock any service to make it work or be sure that we are not leaving any "flag" on before we deploy to production.

We can also leverage this mocking in our e2e without having to do anything at all. Since e2e depends on a running app, as long as we run it using our mock configuration, our e2e tests will use this mock data.

For unit test, we can use our mock as well, but I still think that unit test shouldn't bother with real calls, whether or not they are mock or real.

You can grab the final example from github.

Discussion (0)