Automated Front-End Unit Testing in Angular Framework (Part 1)
- Posted by Yomna Anwar
- On March 30, 2021
Automated Developer Tests is the single most powerful way to ensure built-in quality within your application. This is a scientifically proven fact. Not only you will prevent bugs when adopting developer tests but also, you will make sure that your code is written in a decoupled manner which makes your code a lot neater and easier to maintain and add functionality to.
One of the things we love about Angular is that it is opinionated. Meaning that this framework has its own way of doing things. And when it comes to testing, Angular projects come bundled with a set of libraries that enable developers to easily integrate all types of tests within their angular apps. Let us dive deep into Angular Unit Testing.
What are the types of testing?
End to end testing
This type of testing focuses on the behaviour of the whole application. It simulates how the application will behave when a real user is using the application. It is a type of integration testing because when writing these tests, you try not to mock anything, and you try to make the application behave as it would in the production. Also, this type of testing comes under test last testing because you will be able to write these tests only when your application is completed. For this type of testing, angular comes bundled with a library called Protractor.
Unit testing
This type of testing focuses on the component behaviour rather than the application behaviour. You write tests to ensure that a specific component in your application behaves as expected.
The hardest part here is adjusting the component state to be able to test it under specific circumstance, you do not have to make this effort when writing E2E tests however unit tests help you discover bugs early and write better quality code.
This is due to the nature of the unit tests itself as it comes early in the process. Also, you must mock your component dependencies which means that the code itself must be testable.
For this type of testing, Angular comes bundled with Jasmin and Karma to make writing and running tests easier.
What are Jasmine & Karma?
In this series, we will focus on front end Unit tests so here is some information about the libraries chosen by Angular to make unit testing easier.
Jasmine
1. A behaviour-driven development framework
2. The syntax is English like, so the code is readable by almost anyone
3. Does not depend on any other framework
4. Can be used outside angular projects and still going to perform very well
Karma
1. It is the task runner for your tests.
2. It performs tasks like setting up the testing server, change detection, running the tests, generate tests reports.
Angular CLI
If you have ever developed a front-end application using Angular then must have used Angular CLI before. This tool generates all the boilerplate code you need to get started with your Angular development. One of the things this tool takes care of when generating your project is the testing configuration. Below is the list of files that you need to know about when writing Angular tests.
.spec.ts files
Spec files are the files that contain our test suites. Every component you create in Angular contains a spec file that contains the unit tests for these components. All spec files that are auto-generated by Angular CLI contains at least one unit that asserts that the component creation happened successfully.
karma.conf.js file
A file that contains all the configuration for the test runner.
test.ts file
A file that is required by “karma.conf”.
Test suite anatomy
In Angular, all our component unit tests should live in the component spec file. Let us look into the spec file that is generated by the Angular CLI.
Declarations
/** * the describe function is part of the jasmine framework and it's used * to define a test suite. * it takes two parameters * @param description which describes the component under test in text * @param specDefinition which includes the actual test suite */ describe('AppComponent', () => { /** * first declared variable is the component under test */ let component: AppComponent; /** * Fixture is the test environment for the component above, * it provides access to the component itself */ let fixture: ComponentFixture; /** * The component's rendered HTML is represented in the Debug element */ let debugElement: DebugElement; /** * declare any mocks/spies here */ let countriesServiceSpy: CountriesService; const dummyCounties: Array = [ {name: 'Egypt', flag: 'c/cf/Flag_of_Egypt.svg', population: 100000000, area: 1234} ]; });
TestBed Configuration
describe('AppComponent', () => { /** * like junit, before each is a function that will execute right before your tests do */ beforeEach(async () => { /** * a test bed is an angular module specific for this test environment to test this component. * the configureTestingModule function is used to pass on any configuration specific to the TestBed * or the module before the tests starts running. */ await TestBed.configureTestingModule({ /** * here we're specify the component we would like to test in isolation. meaning that, * when the TestBed is created the AppComponent will be present there and not just * a mock. */ declarations: [ AppComponent ], /** * here we're specifying all the modules that we need to import in our testing module for * it to run without issues. */ imports: [ NgbModule, RouterModule.forRoot([]) ], /** * allows your testing module to include non angular modules. * this can be removed if everything included in your angular is native to angular itself */ schemas: [ CUSTOM_ELEMENTS_SCHEMA ], /** * here we specify any mock object that we would like to inject in our testing module */ providers: [ CountriesService ] }).compileComponents(); // compiles the template and the CSS }); });
Initializations
describe('AppComponent', () => { /** * another before each block, it contains all variables setting that will be used for testing. * it's separated from the previous beforeEach for readability. */ beforeEach(() => { /** * set up your spy here * there are two ways to configure mocks when testing angular * * the first approach is to use fakes: * Fakes are dummy classes created manually and in which we implemented the desired mock behaviour * Fakes are useful when mocking simple services. * * the second approach is spying : * a Spy is a class that listens to the calls made to the actual injected dependency then * intercepts the call and returns a mock response * * in the below example we're using spying to listen to the authentication service and always return * true for any call made to that service */ countriesServiceSpy = TestBed.get(CountriesService); spyOn(countriesServiceSpy, 'getCountries').and.returnValue(dummyCounties); // initializes the test fixture 'test environment' fixture = TestBed.createComponent(AppComponent); // use this variable to test the component in isolation component = fixture.componentInstance; // use this variable to test the component's rendered HTML debugElement = fixture.debugElement; // runs angular change detection fixture.detectChanges(); }); });
Tests
describe('AppComponent', () => { // tests that component creation happens successfully it('should create the app', () => { expect(component).toBeTruthy(); }); });
Conclusion
So far, we have discovered the types of front-end testing and the testing setup that comes with angular, out of the box, to help us write and run tests. To learn how we can test our components and how we can turn these unit tests into automated developer tests, stay tuned for part 2.