top of page
Writer's pictureViq Hus

Integrating Jest and Cucumber within Angular 8

Updated: Jul 27, 2023

Integrating Jest and Cucumber within Angular 8

Today’s post is about how to get your tests up and running using Jest and writing them in cucumber.

Jest is a tool developed at FaceBook; it’s a JS testing framework used mainly in React. Jest also comes with a test runner similar to Karma but has many benefits such as fast test execution (including parallelized test runs), better test reports, built in code coverage and my favourite: not running the test in a browser. Cucumber on the other hand is a language that supports BDD (always go BDD!). It’s a simple set of human readable specifications written in plain text.

This article assumes you have basic knowledge of Angular.

There are many articles out there already which demonstrate how to integrate Jest within Angular, but he, we will look specifically at integrating tests written cucumber, and run using Jest in Angular. One other thing I also like about Jest is how it focusses on the JavaScript as opposed to the HTML or CSS. In this article, we will go through building a very simple shopping list app, where you can add a new item and price to your list and the total price of all your items will then be calculated.

1. Firstly, we need to create a new Angular app. In your VS Code console, type the command:

ng new my-angular-project

Select options for your project as appropriate.

2. Next, we need to add functionality to our app. We don’t need to add the HTML/CSS components as we don’t care about in our case, but we’ll add it in just for the sake of completeness. If you create a new shopping-list component using the Angular generator.

I’m going to be using Material Design to theme my component.

Here is my HTML:

<mat-card-header> <mat-card-title>My Shopping List</mat-card-title> <mat-card-subtitle>Total:
        £{{totalPrice}}</mat-card-subtitle> </mat-card-header> <mat-card-content>
    <form class="example-form"> <mat-form-field class="example-full-width"> <input [(ngModel)]="itemToAdd.item"
                name="item" matInput placeholder="Item"> </mat-form-field> <mat-form-field class="example-full-width">
            <textarea [(ngModel)]="itemToAdd.price" name="price" matInput type="number" placeholder="Price"></textarea>
        </mat-form-field> </form> <mat-list role="list"> <mat-list-item *ngFor="let item of listOfTotalItems"
            role="listitem">{{item.item}} - {{item.price}}</mat-list-item> </mat-list>
</mat-card-content> <mat-card-actions> <button (click)="addItem()" mat-raised-button color="primary">Add Item</button>
</mat-card-actions> </mat-card>

3. Now we need to implement the functionality of adding an item to our list and calculating the total price. Here is my component typescript file:


@Component({
    selector: 'app-shopping-list',
    templateUrl: './shopping-list.component.html',
    styleUrls: ['./shopping-list.component.scss']
 }) export class ShoppingListComponent {
    constructor() {}
    itemToAdd: ShoppingListItem = new ShoppingListItem();
    listOfTotalItems: ShoppingListItem[] = [];
    totalPrice: number = 0;
    addItem() {
       this.totalPrice = this.totalPrice + parseFloat(this.itemToAdd.price);
       this.listOfTotalItems.push(this.itemToAdd);
       this.itemToAdd = new ShoppingListItem();
    }
 }

We are now finished with our app. Your finished app should function similar to this:





Now we are ready to do some testing (ps. In theory, you should always be writing your tests FIRST, then implementing after using Test Driven Development).

4. Next, we need to install the jest package, along with jest-angular-preset which includes some useful preset Jest configuration for Angular projects. The third package is jest-cucumber which is an excellent package as an alternative to cucumber.js which runs on top of Jest. Install all 3 packages as dev dependencies:

npm install jest jest-angular-preset jest-cucumber

5. We also need to remove all karma/jasmine packages from our project, as these can cause conflicts due to the same naming of many items.

6. In your packages.json file, in the “scripts” sections, changes “test”: “ng test” to “test”: “jest” so the npm run test will use the Jest test runner.

7. Add “jest” config to package.json. You need to tell Jest to use your preset configuration from the package jest-preset-angular, among many other options. Here is my jest configuration:

"jest": {
    "preset": "jest-preset-angular",
    "setupFilesAfterEnv": [
        "/setupJest.ts"
    ],
    "collectCoverage": true,
    "collectCoverageFrom": [
        "/**/*.ts",
        "!/**/*.d.ts",
        "!/**/*.spec.ts"
    ],
    "testMatch": [
        "**/*.steps.ts"
    ]
}

8. Add a test:

I store my test files in the same folder as the component. A .feature file for the cucumber written test and a steps.ts file, to store the tests definition.

I have 3 pieces of functionality to test:

1. The component loads

2. New items add are added to my list.

3. The total price is calculated correctly when I add a new item to the shopping list.

You can now add a new “shopping-list.feature” file and add the above 3 tests:

Feature: Shopping List        

Scenario: Loading the shopping list component          
    When I go to the shopping list component          
    Then I should be able to view the shopping list component        
    
Scenario: Adding a new item to the shopping list          
    When I go to the shopping list component          
    And I add the following item to the shopping list              
      | item | price |              
      | Milk | 1.39  |          
    Then the shopping list should have the following items              
      | item | price |              
      | Milk | 1.39  |        
      
Scenario: Calculating the total price of the shopping list          
    When I go to the shopping list component          
    Given the current total price is '2.84'          
    When I add the following item to the shopping list              
      | item  | price |              
      | Bread | 0.85  |          
    Then the total price of the shopping list should be '3.69'      

Now add a “shopping-list.steps.ts” file to store your test definition in (written using Jest), which can then be picked up by the Jest test runner. These tests function in a similar way to Jasmine but differ slightly in syntax. Here is my steps file:

const feature = loadFeature('./shopping-list.feature', {
    loadRelativePath: true
 });
 let component: ShoppingListComponent;
 let fixture: ComponentFixture;
 defineFeature(feature, test => {
    beforeEach(() => {
       TestBed.configureTestingModule({
          declarations: [ShoppingListComponent],
          schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
       }).compileComponents();
    }) test('Loading the shopping list component', ({
       given,
       when,
       then
    }) => {
       whenIGoToTheShoppingListComponent(when);
       thenIShouldBeAbleToViewTheShoppingListComponent(then);
    });
    test('Adding a new item to the shopping list', ({
       given,
       when,
       then
    }) => {
       whenIGoToTheShoppingListComponent(when);
       whenIAddTheFollowingItemToTheShoppingList(when);
       thenTheShoppingListShouldHaveTheFollowingItems(then);
    });
    test('Calculating the total price of the shopping list', ({
       given,
       when,
       then
    }) => {
       whenIGoToTheShoppingListComponent(when);
       givenTheCurrentTotalPriceIs(given);
       whenIAddTheFollowingItemToTheShoppingList(when);
       thenTheTotalPriceOfTheShoppingListShouldBe(then);
    });
 });
 const whenIGoToTheShoppingListComponent = when => {
    when(/I go to the shopping list component/, () => {
       fixture = TestBed.createComponent(ShoppingListComponent);
       component = fixture.componentInstance;
       fixture.detectChanges();
    });
 }
 const thenIShouldBeAbleToViewTheShoppingListComponent = then => {
    then(/I should be able to view the shopping list component/, () => {
       expect(component).toBeTruthy();
    });
 }
 const whenIAddTheFollowingItemToTheShoppingList = when => {
    when(/I add the following item to the shopping list/, (table: ShoppingListItem[]) => {
       const newItem = table[0];
       component.itemToAdd = newItem;
       component.addItem();
    });
 }
 const thenTheShoppingListShouldHaveTheFollowingItems = then => {
    then(/the shopping list should have the following items/, (table: ShoppingListItem[]) => {
       const expectedShoppingItems = table;
       expect(component.listOfTotalItems).toHaveLength(expectedShoppingItems.length);
       expectedShoppingItems.forEach(expectedItem => {
          expect(expectedShoppingItems.some(x => x.item == expectedItem.item && x.price == expectedItem.price)).toBe(true);
       });
    });
 }
 const givenTheCurrentTotalPriceIs = given => {
    given(/the current total price is '(.*)'/, (currentTotalPrice: string) => {
       component.totalPrice = parseFloat(currentTotalPrice);
    });
 }
 const thenTheTotalPriceOfTheShoppingListShouldBe = then => {
    then(/the total price of the shopping list should be '(.*)'/, (expectedTotalPrice: string) => {
       expect(component.totalPrice).toBe(parseFloat(expectedTotalPrice));
    });
 }

9. Run the tests! If you run the tests using npm run test, Jest will run these tests (and no other tests including the Angular generated specs as our Jest runner will only match “**steps.ts”), and we should be able to see that we have 100% code coverage in our shopping-list component!




You can find the full source code for this Angular app including the test on GitHub here.

15 views0 comments

Recent Posts

See All

Comments


I'm a lead software developer currently working at AG Grid in London.

Technologies I'm currently focused on include Salesforce, .NET Core, Angular, SQL, React and Azure.

Other than that, in my spare time I watch the Arsenal at the Emirates, work on side projects or watch sports. Oh, and I'm also a part-time body builder.

You can contact me at vh@viqas.co.uk

profile.jpg

About Viqas Hussain

bottom of page