Create unit tests

It is good practice to add unit tests as you go along. Follow the procedures below to create tests and run them.

I. Start test file

To start your test file, follow these steps:

  1. Create a test directory in the shoppingcart directory.

  2. Download the latest version of the testkit.js file and place it into the test directory:

    curl -o ./test/testkit.js -L https://raw.githubusercontent.com/lightbend-labs/akkaserverless-ecommerce-javascript/master/cart/test/testkit.js
  3. Create a cart.test.js file in the test directory

  4. Add import statements to the file to make use of Mocha and other libraries:

    import { MockEventSourcedEntity } from './testkit.js';
    import { expect } from 'chai';
    import cart from '../cart.js';

II. Add unit tests

  1. Add a describe function that contains the unit tests for the shopping cart.

    describe('Test Warehouse', () => {
    
    });
  2. In the describe function add a few constants so you can reuse messages:

    const entityId = '1';
    const newItem = { 'userId': entityId, 'productId': 'turkey', 'name': 'delicious turkey', 'quantity': 2 }
    const removeItem = { 'userId': entityId, 'productId': 'turkey' }
  3. In the describe function add a new describe function:

    describe('Commands', () => {
      // Add tests here
    });
  4. In the describe function you just added, add test cases to validate you can add an item to the cart:

    it('Should add an item...', () => {
        const entity = new MockEventSourcedEntity(cart, entityId);
        const result = entity.handleCommand('AddItem', newItem);
    
        // The cart returns an empty message
        expect(result).to.be.empty;
    
        // There shouldn't be any errors
        expect(entity.error).to.be.undefined;
    
        // The new state of the entity should match the added item
        expect(entity.state.items[0].name).to.equal(newItem.name);
    
        // There should be one event
        expect(entity.events.length).to.be.equal(1)
        expect(entity.events[0].constructor.name).to.be.equal('ItemAdded')
    })
  5. Similarly, add test cases to validate you can remove an item from the cart:

    it('Should remove an item...', () => {
        const entity = new MockEventSourcedEntity(cart, entityId);
        let result = entity.handleCommand('AddItem', newItem);
        result = entity.handleCommand('RemoveItem', removeItem);
    
        // The cart returns an empty message
        expect(result).to.be.empty;
    
        // There shouldn't be any errors
        expect(entity.error).to.be.undefined;
    
        // There should be two event
        expect(entity.events.length).to.be.equal(2)
        expect(entity.events[0].constructor.name).to.be.equal('ItemAdded')
        expect(entity.events[1].constructor.name).to.be.equal('ItemRemoved')
    })
  6. Finally, add test cases to validate you can get the details from a cart:

    it('Should get cart details...', () => {
        const entity = new MockEventSourcedEntity(cart, entityId);
        let result = entity.handleCommand('AddItem', newItem);
        result = entity.handleCommand('GetCart', { userId: entityId });
    
        // The cart returns an empty message
        expect(result).to.not.be.empty;
    
        // There shouldn't be any errors
        expect(entity.error).to.be.undefined;
    })
  7. Now that the unit tests for the command handlers are in place, you also want to test the event handlers. To do so, add a new describe function called Events inside of the first describe function. Your code should look like:

    describe('Test Warehouse', () => {
      describe('Commands', () => {
        // In the previous steps you added tests here
      });
    
      describe('Events', () => {
        // Now you'll add tests here
      });
    });
  8. In the Events describe function you just added, add test cases to validate you can handle the ItemAdded event:

    it('Should handle an item added...', () => {
        const entity = new MockEventSourcedEntity(cart, entityId);
    
        // Mock a new ItemAdded
        function ItemAdded(addItem) {
            this.item = {
                productId: addItem.productId,
                name: addItem.name,
                quantity: addItem.quantity
            }
        }
        const result = entity.handleEvent(new ItemAdded(newItem))
    
        // There shouldn't be any errors
        expect(entity.error).to.be.undefined;
    
        // The new state of the entity should match the new product
        expect(entity.state.items[0].name).to.equal(newItem.name);
    })
  9. Similarly, add test cases to validate you can handle the ItemRemoved event:

    it('Should handle an item removed...', () => {
        const entity = new MockEventSourcedEntity(cart, entityId);
    
        // Mock a new ItemAdded
        function ItemAdded(addItem) {
            this.item = {
                productId: addItem.productId,
                name: addItem.name,
                quantity: addItem.quantity
            }
        }
        let result = entity.handleEvent(new ItemAdded(newItem))
    
        // There shouldn't be any errors
        expect(entity.error).to.be.undefined;
    
        // Mock a new ItemRemoved
        function ItemRemoved(addItem) {
            this.productId = addItem.productId
        }
        result = entity.handleEvent(new ItemRemoved(newItem))
    
        expect(entity.events.length).to.be.equal(2)
        expect(entity.events[0].constructor.name).to.be.equal('ItemAdded')
        expect(entity.events[1].constructor.name).to.be.equal('ItemRemoved')
    })

III The complete cart.test.js file

Your complete cart.test.js file should look like the following:

import { MockEventSourcedEntity } from './testkit.js';
import { expect } from 'chai';
import cart from '../cart.js';

describe('Test Warehouse', () => {
const entityId = '1';
const newItem = { 'userId': entityId, 'productId': 'turkey', 'name': 'delicious turkey', 'quantity': 2 }
const removeItem = { 'userId': entityId, 'productId': 'turkey' }

describe('Commands', () => {
it('Should add an item...', () => {
    const entity = new MockEventSourcedEntity(cart, entityId);
    const result = entity.handleCommand('AddItem', newItem);

    // The cart returns an empty message
    expect(result).to.be.empty;

    // There shouldn't be any errors
    expect(entity.error).to.be.undefined;

    // The new state of the entity should match the added item
    expect(entity.state.items[0].name).to.equal(newItem.name);

    // There should be one event
    expect(entity.events.length).to.be.equal(1)
    expect(entity.events[0].constructor.name).to.be.equal('ItemAdded')
})

it('Should remove an item...', () => {
    const entity = new MockEventSourcedEntity(cart, entityId);
    let result = entity.handleCommand('AddItem', newItem);
    result = entity.handleCommand('RemoveItem', removeItem);

    // The cart returns an empty message
    expect(result).to.be.empty;

    // There shouldn't be any errors
    expect(entity.error).to.be.undefined;

    // There should be two event
    expect(entity.events.length).to.be.equal(2)
    expect(entity.events[0].constructor.name).to.be.equal('ItemAdded')
    expect(entity.events[1].constructor.name).to.be.equal('ItemRemoved')
})

it('Should get cart details...', () => {
    const entity = new MockEventSourcedEntity(cart, entityId);
    let result = entity.handleCommand('AddItem', newItem);
    result = entity.handleCommand('GetCart', { userId: entityId });

    // The cart returns an empty message
    expect(result).to.not.be.empty;

    // There shouldn't be any errors
    expect(entity.error).to.be.undefined;
})
})
describe('Events', () => {
it('Should handle an item added...', () => {
    const entity = new MockEventSourcedEntity(cart, entityId);

    // Mock a new ItemAdded
    function ItemAdded(addItem) {
        this.item = {
            productId: addItem.productId,
            name: addItem.name,
            quantity: addItem.quantity
        }
    }
    const result = entity.handleEvent(new ItemAdded(newItem))

    // There shouldn't be any errors
    expect(entity.error).to.be.undefined;

    // The new state of the entity should match the new product
    expect(entity.state.items[0].name).to.equal(newItem.name);
})

it('Should handle an item removed...', () => {
    const entity = new MockEventSourcedEntity(cart, entityId);

    // Mock a new ItemAdded
    function ItemAdded(addItem) {
        this.item = {
            productId: addItem.productId,
            name: addItem.name,
            quantity: addItem.quantity
        }
    }
    let result = entity.handleEvent(new ItemAdded(newItem))

    // There shouldn't be any errors
    expect(entity.error).to.be.undefined;

    // Mock a new ItemRemoved
    function ItemRemoved(addItem) {
        this.productId = addItem.productId
    }
    result = entity.handleEvent(new ItemRemoved(newItem))

    expect(entity.events.length).to.be.equal(2)
    expect(entity.events[0].constructor.name).to.be.equal('ItemAdded')
    expect(entity.events[1].constructor.name).to.be.equal('ItemRemoved')
})
})
})

IV. Run tests

  1. To run the unit tests, open a terminal window and navigate to the shoppingcart directory. From there run npm run test to see all tests pass:

    $ npm run test
    
    > ecommerce-cart@2.0.0 pretest
    > compile-descriptor ./cart.proto ./domain.proto
    
    Compiling descriptor with command: /home/retgits/Downloads/akkaserverless-ecommerce-javascript/cart/node_modules/@lightbend/akkaserverless-javascript-sdk/protoc/bin/protoc --include_imports --proto_path=/home/retgits/Downloads/akkaserverless-ecommerce-javascript/cart/node_modules/@lightbend/akkaserverless-javascript-sdk/proto --proto_path=/home/retgits/Downloads/akkaserverless-ecommerce-javascript/cart/node_modules/@lightbend/akkaserverless-javascript-sdk/protoc/include --descriptor_set_out=user-function.desc --include_source_info --proto_path=. ./cart.proto ./domain.proto
    
    > ecommerce-cart@2.0.0 test
    > mocha ./test/*.test.js
    
    
    
      Test Warehouse
        Commands
          ✓ Should add an item...
          ✓ Should remove an item...
          ✓ Should get cart details...
        Events
          ✓ Should handle an item added...
          ✓ Should handle an item removed...
    
    
      5 passing (11ms)