Angular Unit Testing @ViewChild

Angular Unit Testing @ViewChild

If you have been using Angular for any length of time, you have probably run into a parent component that references a child component using @ViewChild

Unit testing the parent component is trivial, if you just want to include the child component in the test. However, it gets tricky when we want to mock the child component with a stub.

In this article we will discuss:

  • A simple introduction to using @ViewChild
  • Unit testing your parent component without mocking the child
  • Unit testing your parent component with a stub child component

To get the most out of this article, you should have at least an introductory understanding of Angular Unit Testing.

Introducing @ViewChild

If you are already familiar with @ViewChild and how it is used, feel free to skip this section.

Simply put, @ViewChild lets us make calls to the functions on a child component. Instead of only using the component’s @Inputs(), we can treat the component more like an API and access its public interface.

Example child component

Take a look at this simple component:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `Timestamp: {{timeStamp}}`
})
export class ChildComponent implements OnInit {
  public timeStamp: Date;

  ngOnInit() {
    this.updateTimeStamp();
  }

  updateTimeStamp() {
    this.timeStamp = new Date();
  }
}

child.component.ts

Notice that ChildComponent just displays the current timestamp when it loads. It also has a public function updateTimeStamp()that causes the timestamp to refresh.

Example parent component

Now let’s assume we have a parent component that wants to display ChildComponent and also wants to be able to tell it to update the displayed timestamp. We can do something like this:

import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'app-parent',
  template: `
    <button type="button" (click)="update()">Update</button>
    <br>
    <app-child></app-child>`
})
export class ParentComponent {
  @ViewChild(ChildComponent) childComponent: ChildComponent;

  update() {
    this.childComponent.updateTimeStamp();
  }
}

parent.component.ts

As you can see, ParentComponent shows a button with text: Update. Clicking this button calls the update() function in ParentComponent which in turn calls the updateTimeStamp() function on ChildComponent.

The line that gives ParentComponent access to the instance of ChildComponent is:

@ViewChild(ChildComponent) childComponent: ChildComponent;

Here is a working example on StackBlitz:

Testing @ViewChild with a Component

Now we want to write a simple unit test for ParentComponent. To keep things simple, we won’t worry about testing button’s click event handler. We will just test that the update() function in ParentComponent calls the updateTimeStamp() function in the ChildComponent.

Our .spec file looks like this:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ParentComponent } from './parent.component';
import { ChildComponent } from '../child/child.component';

describe('ParentComponent', () => {
  let component: ParentComponent;
  let fixture: ComponentFixture<ParentComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        ParentComponent,
        ChildComponent
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ParentComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should call updateTimeStamp', () => {
    spyOn(component.childComponent, 'updateTimeStamp');
    component.update();
    expect(component.childComponent.updateTimeStamp).toHaveBeenCalled();
  });
});

parent.component.spec.ts

Notice that our ParentComponent unit tests reference ChildComponent in the declarations on line 13.

In lines 30–32 we are just testing that the update() function calls the updateTimeStamp() function in ChildComponent.

We can run our unit tests using:

$ npm run test

And we see that all of our unit tests pass:

So far so good.

Testing @ViewChild with a Stub Component

Testing our ParentComponent by just including ChildComponent in the declarations isn’t optimal. This is because problems in ChildComponent will cause problems in our ParentComponent tests. For unit testing we would really prefer to isolate our component from its children.

Typically, we mock out our component’s dependencies by creating stubs for the child components. Here is an example of mocking ChildComponent with a simple stub:

import { Component } from '@angular/core';

@Component({
  selector: 'app-child',
  template: ''
})
export class ChildStubComponent {
  updateTimeStamp() {}
}

Hopefully, you have used stub components like this before. But, for clarity I will quickly explain how it works. Notice that the stub is just a minimal implementation of the real thing:

  • A simple introduction to using @ViewChild
  • Unit testing your parent component without mocking the child
  • Unit testing your parent component with a stub child component

Testing with the stub

Now let’s try to use this stub component in our unit test for ParentComponent. In our declarations we can just replace ChildComponent with ChildStubComponent. For example, the asynchronous beforeEach() function in ParentComponentshould now look like this:

beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [
      ParentComponent,
      ChildStubComponent
    ]
  })
  .compileComponents();
}));

And now when we run our unit tests with:

$ npm run test

It fails. Argh!

Providing the @ViewChild for Testing

Hmm, so the unit test can’t find the instance of ChildStubComponent. OK, we will need to somehow provide the @ViewChild for the test component.

There are actually a couple of ways to do this:

  • A simple introduction to using @ViewChild
  • Unit testing your parent component without mocking the child
  • Unit testing your parent component with a stub child component

Of these, adding the provider is my preferred way. But, because this is Angular In Depth, and not Angular the Way Todd Thinks You Should Do It, we will look at both ways and I will let you be the judge.

Manually populating the child component

We will change our synchronous beforeEach() function like this:

beforeEach(() => {
  fixture = TestBed.createComponent(ParentComponent);
  component = fixture.componentInstance;
  // populate childComponent with an instance of the stub  
  component.childComponent =
      TestBed.createComponent(ChildStubComponent).componentInstance;
  fixture.detectChanges();
});

As you can see, we are using:

TestBed.createComponent(ChildStubComponent).componentInstance; to create an instance of the stub component.

OK, let’s run those unit tests again:

$ npm run test

ERROR in src/app/parent/parent.component.spec.ts(23,5): error TS2739: Type 'ChildStubComponent' is missing the following properties from type 'ChildComponent': timeStamp, ngOnInit

Compile error! Are you kidding me?

Matching the type of ChildComponent

We get the compile error because TypeScript is rather strict about its type checking (hence the name). Even though timeStamp and ngOnInit() aren’t necessary for the testing, they are part of the public interface of ChildComponent and TypeScript expects them to be there.

We could just add the missing ngOnInit() and timeStamp members to our stub component. But, it is easier and more fun to cast our ChildStubComponent to a ChildComponent.

Casting the component

TypeScript, like most strongly typed languages, provides the ability to type cast objects. There are a couple of ways to do this in TypeScript.

You can wrap the cast in <>. For example:

const myFoo: Foo = <Foo> bar;

You can also use the as keyword like this:

const myFoo: Foo = bar as Foo;

My tslint prefers the as keyword. As my friend Tim Deschryver pointed out, this is because of this line:

"no-angle-bracket-type-assertion": true, in my tslint.json file.

Making the change in the parent.component.spec.ts file looks like this:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ParentComponent } from './parent.component';
import { ChildComponent } from '../child/child.component';
import { ChildStubComponent } from '../child/child-stub.component.spec';

describe('ParentComponent', () => {
  let component: ParentComponent;
  let fixture: ComponentFixture<ParentComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        ParentComponent,
        ChildStubComponent
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ParentComponent);
    component = fixture.componentInstance;
    component.childComponent = TestBed.createComponent(ChildStubComponent).componentInstance as ChildComponent;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should call updateTimeStamp', () => {
    spyOn(component.childComponent, 'updateTimeStamp');
    component.update();
    expect(component.childComponent.updateTimeStamp).toHaveBeenCalled();
  });
});

parent.component.spec.ts

And our unit tests compile and run.

Adding a provider to the stub component

My preferred way to handle this is to specify a provider in the stub component. Alexander Poshtaruk came up with the idea for this. And, thekiba actually showed us exactly how to do it.

You have probably seen and even used components that look like this:

@Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [
    {
      provide: HeroService,
      useClass: BetterHeroService
    }
  ]
})
export class HeroListComponent {
/* . . . */
}

You’ll notice that this component has its own providers array in the metadata. In the providers array there is an object that looks like this:

{ provide: HeroService, useClass: BetterHeroService }

This object is known as a Dependency Provider. The useClass is the provider-definition key. If you have ever mocked services for your unit tests, you have probably used this.

Actually, thekiba recommended useExisting. But, I didn’t want to have to explain the difference here. And, this article is already getting too long and detailed. And, I might want to use that topic for a possible future article. And, I am afraid that you will stop following me if I dive any further down this rabbit hole.

OK, back to the providers array:

As I was saying before I so rudely interrupted myself: you usually put services in the providers array. But surprisingly, you can also put dependency providers for components in there.

Let’s modify ChildStubComponent to use a useClass provider for ChildComponent. It looks like this:

import { Component } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'app-child',
  template: '',
  providers: [
    {
      provide: ChildComponent,
      useClass: ChildStubComponent
    }
  ]
})
export class ChildStubComponent {
  updateTimeStamp() {}
}

child-stub.component.spec.ts

Here, we are telling Angular that if this class is part of the build, it should provide ChildStubComponent for ChildComponent. Because this code is in a .spec file, it will only take effect during testing.

Finally, I just want to remind you that when we use the provider, we do not need to populate the child component instance manually anymore with TestBed.createComponent.

Run the Unit Tests

Now when we run the tests using:

$ npm run test

We see that our unit tests all compile and pass:

Summary

So, to test a component that uses @ViewChild just do the following:

  1. Mock out the child component with a simple stub component.
  2. Add the stub component to the declarations in your parent component .spec file.
  3. Add a useClass Dependency Provider in the stub component’s providers array like this:
  4. { provide: HeroService, useClass: BetterHeroService }

Resources

A Quick Guide to Angular 7 in 4 Hours

Complete Angular 7 - Ultimate Guide - with Real World App

Go Full Stack with Spring Boot and Angular 7

Angular 6 (Angular 2+) & React 16 - The Complete App Guide

Angular (Full App) with Angular Material, Angularfire & NgRx

What are the best alternatives for angular js?

<img src="https://moriohcdn.b-cdn.net/193902114c.png">There are numerous frameworks and libraries used across the globe. If not angular, there are platforms like React, Vue, Aurelia and so on for app development.

There are numerous frameworks and libraries used across the globe. If not angular, there are platforms like React, Vue, Aurelia and so on for app development.

Angular 8 Node & Express JS File Upload

Angular 8 Node & Express JS File Upload

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

We are also going to take the help of Node.js to create the backend server for Image or File uploading demo. Initially, we’ll set up an Angular 8 web app from scratch using Angular CLI. You must have Node.js and Angular CLI installed in your system.

We’ll create the local server using Node.js and multer middleware. Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. Once we are done setting up front-end and backend for our File uploading demo then, we’ll understand step by step how to configure file uploading in Angular 8 app using Node server.

Prerequisite

In order to show you Angular 8 File upload demo, you must have Node.js and Angular CLI installed in your system. If not then check out this tutorial: Set up Node JS

Run following command to install Angular CLI:

npm install @angular/cli -g

Install Angular 8 App

Run command to install Angular 8 project:

ng new angular-node-file-upload

# ? Would you like to add Angular routing? No
# ? Which stylesheet format would you like to use? CSS
cd angular-node-file-upload

Show Alert Messages When File Uploaded

We are going to install and configure ngx-toastr an NPM package which helps in showing the alert message when the file is uploaded on the node server.

npm install ngx-toastr --save

The ngx-toastr NPM module requires @angular/animations dependency:

npm install @angular/animations --save

Then, add the ngx-toastr CSS in angular.json file:

"styles": [
    "src/styles.css",
    "node_modules/ngx-toastr/toastr.css"
]

Import BrowserAnimationsModule and ToastrModule in app.module.ts file:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
 
@NgModule({
  imports: [
    CommonModule,
    BrowserAnimationsModule, // required animations module
    ToastrModule.forRoot() // ToastrModule added
  ]
})

export class AppModule { }

Install & Configure ng-file-upload Directive

In this step, we’ll Install and configure ng-file-upload library in Angular 8 app. Run command to install ng-file-upload library.

npm install ng2-file-upload

Once the ng2-file-upload directive is installed, then import the FileSelectDirective and FormsModule in app.module.ts. We need FormsModule service so that we can create the file uploading component in Angular.

import { FileSelectDirective } from 'ng2-file-upload';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    FileSelectDirective
  ],
  imports: [
    FormsModule
  ]
})

export class AppModule { }

Setting Up Node Backend for File Upload Demo

To upload the file on the server, we need to set up a separate backend. In this tutorial, we will be using Node & Express js to create server locally along with multer, express js, body-parser, and dotenv libraries.

Run command to create backend folder in Angular app’s root directory:

mkdir backend && cd backend

In the next step, create a specific package.json file.

npm init

Run command to install required dependencies:

npm install express cors body-parser multer dotenv --save

In order to get rid from starting the server again and again, install nodemon NPM package. Use –-save-dev along with the npm command to register in the devDependencies array. It will make it available for development purpose only.

npm install nodemon --save-dev

Have a look at final pacakge.json file for file upload demo backend:

{
  "name": "angular-node-file-upload",
  "version": "1.0.0",
  "description": "Angualr 8 file upload demo app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Digamber Rawat",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.0.0",
    "express": "^4.17.1",
    "multer": "^1.4.1"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  }
}

Create a file by the name of server.js inside backend folder:

Configure Server.js

To configure our backend we need to create a server.js file. In this file we’ll keep our backend server’s settings.

touch server.js

Now, paste the following code in backend > server.js file:

const express = require('express'),
  path = require('path'),
  cors = require('cors'),
  multer = require('multer'),
  bodyParser = require('body-parser');

// File upload settings  
const PATH = './uploads';

let storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, PATH);
  },
  filename: (req, file, cb) => {
    cb(null, file.fieldname + '-' + Date.now())
  }
});

let upload = multer({
  storage: storage
});

// Express settings
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

app.get('/api', function (req, res) {
  res.end('File catcher');
});

// POST File
app.post('/api/upload', upload.single('image'), function (req, res) {
  if (!req.file) {
    console.log("No file is available!");
    return res.send({
      success: false
    });

  } else {
    console.log('File is available!');
    return res.send({
      success: true
    })
  }
});

// Create PORT
const PORT = process.env.PORT || 8080;
const server = app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Now, while staying in the backend folder run the below command to start the backend server:

nodemon server.js

If everything goes fine then you’ll get the following output:

[nodemon] 1.19.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
Connected to port 8080

Create Angular 8 File Upload Component

In this last step, we are going to create a file upload component in Angular 8 app using Express js API.

Get into the app.component.ts file and include the following code:

import { Component, OnInit } from '@angular/core';
import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
import { ToastrService } from 'ngx-toastr';

const URL = 'http://localhost:8080/api/upload';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  public uploader: FileUploader = new FileUploader({
    url: URL,
    itemAlias: 'image'
  });

  constructor(private toastr: ToastrService) { }

  ngOnInit() {
    this.uploader.onAfterAddingFile = (file) => {
      file.withCredentials = false;
    };
    this.uploader.onCompleteItem = (item: any, status: any) => {
      console.log('Uploaded File Details:', item);
      this.toastr.success('File successfully uploaded!');
    };
  }

}

Go to app.component.html file and add the given below code:

<div class="wrapper">
  <h2>Angular Image Upload Demo</h2>

  <div class="file-upload">
    <input type="file" name="image" ng2FileSelect [uploader]="uploader" accept="image/x-png,image/gif,image/jpeg" />
    <button type="button" (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
      Upload
    </button>
  </div>

</div>

Now, It’s time to start the Angular 8 app to check out the File upload demo in the browser. Run the following command:

ng serve --open

Make sure your NODE server must be running to manage the backend.

When you upload the image from front-end you’ll see your image files are saving inside the backend > uploads folder.

Conclusion

In this Angular 8 tutorial, we barely scratched the surface related to file uploading in a Node application. There are various other methods available on the internet through which you can achieve file uploading task quickly. However, this tutorial is suitable for beginners developers. I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others.

Angular JS Development Company

If you’re finding AngularJS Development Company for consultation or Development, your search ends here at Data EximIT 

🔗 Click here to know more: AngularJS Development