CSC/ECE 517 Fall 2018/E1853 Write unit tests for menu.rb

From Expertiza_Wiki
Jump to navigation Jump to search

This project wrote unit tests for the menu.rb model in Expertiza. A test plan can be found at the bottom.

Project Introduction

The Menu model is used to create the top bar menu in Expertiza. It does this by obtaining and organizing MenuItems based on the current user's Role. Before this project, there were no unit tests for menu.rb. This project seeks to bring the unit test coverage above 90%.


Team

Barrett Bryson (bbryson)

Komal Kangutkar (kmkangut)

RSpec File

The final result can be found at expertiza/spec/models/menu_spec.rb. This specific spec file can be run by calling the following in the expertiza directory:

  rspec -fd ./spec/models/menu_spec.rb

The addition of the -fd argument will write out full descriptions of the tests and provide an easy look at the work we did.

Design

In total, we wrote 29 tests that covered all the functions in both Menu and its internal Node class. Using the given factories, we created 6 MenuItems (test1 to test6) to pass to the Menu as well as 1 Role used to test the Menu constructor.

  let!(:test1) { create(:menu_item, name: "home1", parent_id: nil, seq: 1) }
  
  admin_role = build(:role_of_administrator, id: 3, name: "Administrator", description: "", parent_id: nil, default_page_id: nil)

We stubbed the MenuItem method, items_for_permissions, to return an array of the 6 test items that we created.

  allow(MenuItem).to receive(:items_for_permissions).with(anything).and_return(items)

Other pieces of the code were stubbed because these pieces should be tested in other model specs and not Menu spec. The double 'temp' is created, which acts as a stand-in for some other class objects which interact with Menu, in order to only test the functionalities of Menu.

Each method in menu.rb has at least two tests checking both a known success case and an edge case or potential failure. As an example, a detailed look at the tests for Menu#initialize is shown below.

  context "when role is nil" do
    it "creates a new menu" do
      menu = Menu.new
      expect(menu.instance_of?(Menu))
    end
  end

This first test revealed an error in the existing menu.rb code in the line shown below.

  items = MenuItem.items_for_permissions(role.try(:cache)[:credentials].try(:permission_ids))

The below line shows the change that we made. This allowed the code to be more robust and prevent NoMethodError from nilClass. Without this change, a menu created with a nil role will throw an error causing the program to fail unnecessarily. This is important because the default value of role is nil and that should not fail.

  items = MenuItem.items_for_permissions(role.try(:cache).try(:[], :credentials).try(:permission_ids))

The second test covers the main use case of Menu. It is supplied with a role and assembles a menu. Only a single role is tested because further testing of roles and menus should instead be handled in integration tests.

    context "when a role is passed as an argument" do
      it "creates a new menu" do
        admin_role = build(:role_of_administrator, id: 3, name: "Administrator", description: '', parent_id: nil, default_page_id: nil)
        menu = Menu.new(admin_role)
        expect(menu.instance_of?(Menu))
      end
    end

The third test checks that when the menu is created with items, these items are put into nodes, arranged appropriately and contained within the menu. In this case, the children of root will contain a single item, the node with id 1, because this node has a nil parent. The other nodes all have non-nil parents.

    context "when menu has items" do
      it "creates a new menu with items" do
        menu = Menu.new
        expect(menu.root.children.length).to eq(1)
      end
    end

The final test checks a menu without any nodes. While this case is unlikely in actual use, it is important that it can be handled without throwing any errors. When a menu without any items is created, the root will never have anything added to its children array so this array will be nil.

    context "when menu has not items" do
      it "creates a new menu without items" do
        allow(MenuItem).to receive(:items_for_permissions).with(anything).and_return([])
        menu = Menu.new
        expect(menu.root.children).to be_nil
      end
    end

These 4 tests alone provide nearly 85% coverage of menu.rb because of how much they rely on a variety of other methods within the class.

Many of the functions in menu.rb return a Menu::Node. The simplest, Menu#get_item, takes a Node id and returns the corresponding Node object. All following functions use this to test that the correct Node is returned. However to prevent circular logic, the Menu#get_item test only checks the id of the returned Node.

Test Plan

Below is a list of the descriptions of all of the tests we wrote

Menu::Node

   #initialize
     when role is nil
       initializes with a nil parent
   #setup
     when item.action_controller is nil
       returns /content_page.name
     when item.action_controller is not nil
       returns /controller.name/controller_action.name
   #content_page
     should update the content_page instance variable
   #controller_action
     when @controller_action already has a value
       remains the same
     when @controller_action is nil
       updates @controller_action
   #site_controller
     when @site_controller is nil
       updates @site_controller
     when @site_controller already has a value
       remains the same
   #add_child
     when node has children
       adds a child
     when node has no children
       can add multiple children
     when node has no children
       adds a child

Menu

 #initialize
   when menu has items
     creates a new menu with item
   when menu has not items
     creates a new menu without items
   when role is nil
     creates a new menu
   when a role is passed as an argument
     creates a new menu 
 #select
   returns a node.id based on the given name
 #get_item
   when menu has items
     returns the correct item
   when menu has no items
     returns nil
   when a nonexistent id is passed
     returns nil
 #get_menu
   when a node is selected
     returns a list of nodes that are the children of the selected node
   when called on a nonexistent node
     returns nil
   when menu has no items
     returns nil
 #selected?
   when id passed is not of a selected menu item
     returns false
   when id passed is of a selected menu item
     returns true
   when passed nil as the id
     return false
 #selected
   when a nonexistent node is selected
     returns nil
   when an item is selected
     returns the name of the currently selected item
 #crumbs
   when a node besides root is selected
     returns a list of nodes based on the selected item
   when root is selected
     returns a list of nodes based on the root


Results

The 29 tests provide 100% coverage of the lines in menu.rb. Before the project the coverage was only

A video of all tests running can be seen here.

The main repository can be found here

The forked git repository for this project can be found here

Below is a snapshot of the coverage of our tests. Full coverage can be found at expertiza/coverage/coverage.html. The specific coverage can be found in menu under the models tab.