What is the method for verifying a function within an injected controller?

Right now, I'm in the process of testing whether the getTodaysHours function on my controller is being called. The goal is for this function to retrieve hours from a mock JSON data and pass if the parameters match. However, I am encountering some issues with the initial step.

vendor.controller

export class VendorController {
    constructor($rootScope, data, event, toastr, moment, _, distanceService, vendorDataService, userDataService, stateManagerService) {
        'ngInject';
        //dependencies
        this.$rootScope = $rootScope;
        this.toastr = toastr;
        this._ = _;
        this.userDataService = userDataService;
        this.vendorDataService = vendorDataService;
        this.stateManagerService = stateManagerService;
        this.event = event;

        //initialize
        data.isDeepLink = true;
        this.data = data;
        this.data.last_update = moment(this.data.updated_at).format('MM/DD/YY h:mm A');
        this.data.distance = distanceService.getDistance(this.data.loc.lng, this.data.loc.lat);
        this.data.todaysHours = this.getTodaysHours();
        this.data.rating_num = Math.floor(data.rating);

        this.hasReviewed = (userDataService.user.reviewed[data._id]) ? true : false;
        this.isGrid = false;
        this.isSearching = false;
        this.hideIntro = true;
        this.menuCollapsed = true;
        this.filterMenuCollapsed = true;

        this.selectedCategory = 'All';
        this.todaysHours = '';
        this.type = '';
        this.searchString = '';

        this.reviewScore = 0;

        this.today = new Date().getDay();

        this.vendorDataService.currentVendor = data;

        //load marker on map
        $rootScope.$broadcast(event.ui.vendor.pageLoad, data);

        //retrieve menu
        vendorDataService.getVendorMenu(data._id)
            .then((res)=> {
                this.data.menu = res.menu;
                this.menuContainer = this.data.menu;
                this.totalResults = this.getTotalResults();
                this.availableMenuCategories = this.getAvailableMenuCategories();
            })
            .catch(() => {
                this.toastr.error('Oops, something went wrong! Unable to load the menu.',  'Error');
            });
    }

    //function to get today's hours
    getTodaysHours() {
        let today = this.data.hours[new Date().getDay()];
        return (today.opening_time || '9:00am') + ' - ' + (today.closing_time || '5:00pm');
    }  
}

The first test passes when mocking the JSON data using $provide constant

describe('vendor controller', () => {
    let vm,
        data = {"_id":"56b54f9368e685ca04aa0b87","lat_lon":"33.713018,-117.841101","hours":[{"day_of_the_week":"sun","closing_time":" 7:00pm","opening_time":"11:00am","day_order":0,"id":48880},...];

    beforeEach(angular.mock.module('thcmaps-ui', ($provide) => {
        $provide.constant('data', new data);
      }));

    //first test
    it('should pass', () => {
        expect(data._id).toEqual('56b54f9368e685ca04aa0b87');
    });

    //second test
    it('should call getTodaysHours', () => {
    expect(vm.getTodaysHours()).toHaveBeenCalled();
    });
});

Next, I attempted to inject the controller (unsure about correct syntax):

beforeEach(angular.mock.module('thcmaps-ui', ($provide) => {
    $provide.constant('data', new data);
  }));
beforeEach(inject(($controller) => {
    vm = $controller('VendorController');
    spyOn(vm,'getTodaysHours').and.callThrough();
}));

This resulted in a forEach error. Also, the second test reported an undefined error while evaluating vm.getTodaysHours():

PhantomJS 2.1.1 (Mac OS X 0.0.0) vendor controller should pass FAILED forEach@/Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular/angular.js:341:24 loadModules@/Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular/angular.js:4456:12 createInjector@/Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular/angular.js:4381:22 workFn@/Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular-mocks/angular-mocks.js:2507:60 /Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular/angular.js:4496:53

PhantomJS 2.1.1 (Mac OS X 0.0.0) vendor controller should call getTodaysHours FAILED forEach@/Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular/angular.js:341:24 loadModules@/Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular/angular.js:4456:12 createInjector@/Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular/angular.js:4381:22 workFn@/Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular-mocks/angular-mocks.js:2507:60 /Users/adminuser/Documents/workspace/thcmaps-ui/bower_components/angular/angular.js:4496:53 TypeError: undefined is not an object (evaluating 'vm.getTodaysHours') in /Users/adminuser/Documents/workspace/thcmaps-ui/.tmp/serve/app/index.module.js (line 9) /Users/adminuser/Documents/workspace/thcmaps-ui/.tmp/serve/app/index.module.js:9:244419

Answer №1

When creating your controller with the $controller function, it's important to inject its dependencies. Take a look at this sample controller:

class MyController {
  constructor($rootScope, $log) {
    // Save the dependencies
    this.$rootScope = $rootScope;
    this.$log = $log;
  }

  // Retrieve value from $rootScope
  getValue() {
    this.$log.debug('Retrieving value');
    return this.$rootScope.foobar;
  }

  // Get current date
  getDate() {
    this.$log.debug('Getting date');
    return Date.now()
  }

  static get $inject() {
    return ['$scope', '$log'];
  }
}

This ES6 controller defines its dependencies in the static $inject getter at the end of the class declaration. AngularJS recognizes these when the controller is instantiated.

In this case, the controller depends on $rootScope and $log. When testing, make sure to inject the dependencies like so:

describe('Spec: MyController', () => {
  var controller;

  beforeEach(inject(($rootScope, $log, $controller) => {
    controller = $controller('MyController', {
      $rootScope,
      $log
    });
  });

  it('should return a value from the $rootScope', () => {
    var value = controller.getValue();
    // ... perform checks
  });

  it('should return the current date', () => {
    var date = controller.getDate();
    // ... perform checks
  });
});

Newer versions of Jasmine allow using the this keyword in tests, simplifying variable management and avoiding global declarations.

By sharing this across test blocks, you can eliminate enclosures and globals. Example:

beforeEach(inject(function ($rootScope, $log, $controller) {
  this.controller = $controller('MyController', {
    $rootScope,
    $log
  });
});

it('should return a value from the $rootScope', function () {
  this.value = controller.getValue();
  // ... perform checks
});

Remember to pass an object containing the dependencies to $controller, allowing for mock services or providers instead of spies.


Apologies for the awkward link regarding Jasmine documentation, technical limitations prevented including a direct anchor link to the relevant section.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Modifications to object persist even after being reassigned

When newScore is updated, I want to make sure that oldScore remains unaffected. Can someone help with this? newScore and oldScore are separate objects, so any changes made to one should not be reflected in the other. To prevent updates from affecting bot ...

Is a specific format necessary for express next(err) to function properly?

Recently, while working on my express sub app that utilizes the http-errors module, I encountered an interesting issue. When passing new Forbidden() to the next() callback, it seemed to vanish without triggering any callbacks. However, passing new Error() ...

How to display JSON data accurately within a UITableView

After creating a UITableView and populating it with JSON data retrieved from an API, everything appears to be functioning correctly. However, issues arise when scrolling or deleting rows - the layout becomes disorganized! The problem seems to be related t ...

Tallying the total number of elements within the Item class both pre and post header classes

Hello, I've been brainstorming a JQuery solution to a particular issue. Below, you can see that I have items that come after each heading, which is currently working fine. <div class="header"></div> <div class="item"></div> &l ...

What is the maximum amount of information that can be stored in a data attribute within the DOM?

Occasionally, I find myself wanting to include a substantial amount of data on the webpage in order to minimize additional AJAX calls for dynamic content. However, I am concerned about potential performance implications. Is there a penalty I should be aw ...

Steps for creating a monochromatic blinking background for notifications

Upon receiving a notification, I retrieve it from the database and activate the blinking effect on the notification box. However, I would like to stop the blinking once the user clicks to view the notification and maintain a single color display. For examp ...

You cannot access the property 'subscribe' on a void type in Angular 2

fetchNews(newsCategory : any){ this.storage.get("USER_INFO").then(result =>{ this.storage.get("sessionkey").then(tempSessionKey =>{ this.email = JSON.parse(result).email; this.newSessionKey = tempSessionKey; this.authKey =JSON.stringify("Basic ...

"Learn the steps to seamlessly add text at the current cursor position with the angular-editor tool

How can I display the selected value from a dropdown in a text box at the current cursor position? I am currently using the following code: enter code selectChangeHandler(event: any) { this.selectedID = event.target.value; // console.log("this.selecte ...

How can you locate and emphasize specific text within various elements using JavaScript?

Created a script to highlight linked text in another HTML page. <p>Click <span class="f1"><a href="#myModal" data-reveal-id="myModal">here</a>/span></p> <div class="leftspread"> <p class="table-caption"& ...

Having difficulty loading CSS and other files while utilizing ngRoute in AngularJS

var votingApp = angular.module('VotingApp', [ 'ngRoute', 'students' ]); votingApp.config(function($routeProvider){ $routeProvider.when('/', { templateUrl : 'index.html', con ...

Modify the color of the table map based on specific conditions in a React.js application

To modify the table entry's color based on the current balance, follow these conditions: If the current balance is less than 100, it should be shown in red. If the current balance is between 100 and 200, display it in yellow. Otherwise, show it in gre ...

Attempting to load the parent window of a framed page from a separate domain results in a permission denial issue in Internet

Having an issue with a login page that is hosted within an iframe on a different domain. After a successful login, I am attempting to load the following page: <html> <head> </head> <body onload="parent.window.loca ...

Why isn't offsetTop working for a div within a table in HTML and Javascript?

When using the offsetTop property to get the absolute position of an object, it works fine when the objects are outside of tables. However, if the object is inside a table, it always returns 1. Why does this happen and how can it be avoided? To see an exa ...

JavaScript Reactive: Managing subscriptions in flatMap streams

In my current setup, I have a state$ stream that contains messages$s (an array of message streams). As the State$ is updated, new messages$ are added to the array. My goal is to have a subscriber handle messages from all messages$ in a single stream, ensu ...

The concept of circular dependencies in ES6 JavaScript regarding require statements

After transferring a significant amount of data onto the UI and representing them as classes, I encountered some challenges with managing references between these classes. To prevent confusing pointers, I decided to limit references to the data classes to ...

Encountering an issue: The output folder specified is invalid while attempting to generate a unified HTML template file with gulp-angular-templatecache

We are utilizing Gulp for our build process and one of the primary tasks is to merge all .html template partials from various module folders into a single .js templateCache file. This ensures that users only need to download one file containing all the HTM ...

What is preventing Safari and Firefox from properly handling audio data from MediaElementSource?

It appears that neither Safari nor Firefox can process audio data from a MediaElementSource with the Web Audio API. var audioContext, audioProcess, audioSource, response = document.createElement('h3'), display = document.createElement( ...

Leveraging ng-model with expressions in ng-repeat in AngularJS.Would you

Currently, I am tasked with creating a form for a multilanguage content management system using angularJS. The language list has been defined within the angular scope as follows: $scope.languages = [ {id:0,'name':'English'}, {id:1, ...

Managing interactions with dynamically created buttons

Our Story Greetings! I am skilled in C# and VB.net, but I am diving into the world of Javascript and React. Currently, I am building a ticket purchase app to enhance my skills. While I was able to quickly create this app using Angular, React has posed mor ...

Utilizing class binding in a unique way by setting class through a custom

Encountering an issue while trying to assign a class using a custom pipe that was created. The approach involves mapping over a signal store to verify if the value matches an id in the store. When the condition is met, one class is returned; otherwise, a ...