Exploring the functionality of private methods within controllers and the ready-to-use template from sidewaffle

Trying to navigate the complexities of testing an Angular app has left me feeling bewildered. It seems like there are conflicting recommendations that I'm trying to reconcile. Following John Papa's style guide, I've been using Sidewaffle templates extensively. However, I'm finding a clash between the suggested template and testability aspects.

The style guide advocates for an

activate()

method in your controller to handle the initialization logic. But the general testing guidelines discourage testing private methods. The challenge arises when trying to test the result of the activate() method, where the outcome is passed through

vm.avengers

variable. In his Pluralsight videos, John Papa uses common.activatecontroller() method with

$q.all()

to combine promises for easy function calls during controller activation phase. Consider a scenario where a function doesn't have a result that can be passed through vm, such as a post message to WebApi for user authentication and token setup. Here's an example:

In the following controller, the only business logic involves calling the func1() method within the activate() method, which in turn invokes OAuthenticationService.authenticate(). From a testing perspective, it's crucial to verify if the service was called or not. How can this be tested?

Here is a similar question where one suggestion involves using this keyword like so:

this.activate()

However, a comment highlights that this approach doesn't work with ControllerAs syntax, which is what I'm following.

Creating a mock for the service with spy on authenticate method reveals that it wasn't called, likely because the method is private.

Feeling stuck...

Appreciate any assistance!

Example

(function () {
    'use strict';

    angular
        .module('app')
        .controller('controller', controller);

    controller.$inject = ['$location']; 

    function controller($location) {
        /* jshint validthis:true */
        var vm = this;
        vm.title = 'controller';

        activate();

        function activate() {

            func1();

        }

        function func1() {

            OAuthAuthenticateService.authenticate(user).then(function() {

                //setting up headers and other stuff, nothing will be part of $scope or vm;

            });

        }
    }
})();

Original code:

(function () {
    'use strict';

    var controllerId = 'requestAuthorizationController';

    angular
        .module('myapp')
        .controller(controllerId, requestAuthorizationController);

    requestAuthorizationController.$inject = ['$rootScope',
                                                '$scope',
                                                'requestAuthorizationService'
                                                'Restangular'];

    function requestAuthorizationController($rootScope,
                                            $scope,
                                            requestAuthorizationService
                                            Restangular) {
        /* jshint validthis:true */
        var vm = this;

//other business logic

        activate();

        function activate() {

            requestAuthorization();

        }

        function requestAuthorization() {

            vm.fired = undefined;

            requestAuthorizationService.getDummy();

        }
    }
})();

Jasmine test:

'use strict';

describe('RequestAuthenticationController Specification', function () {

    var RestangularProviderMock,
        localStorageServiceProvider,
        $httpProvider,
        requestAuthorizationController,
        requestAuthorizationServiceMock,
        $rootScope;

    //modules
    beforeEach(function() {

        angular.module('dilib.layout', []);
        angular.module('http-auth-interceptor', []);

    });


    //providers
    beforeEach(function () {

        module('dilib', function(RestangularProvider, _localStorageServiceProvider_, _$httpProvider_, $provide) {

            RestangularProviderMock = RestangularProvider;
            localStorageServiceProvider = _localStorageServiceProvider_;
            $httpProvider = _$httpProvider_;

            $provide.service('requestAuthorizationService', function() {
                this.getDummy = jasmine.createSpy('getDummy').and.callFake(function(num) {
                });
            });
        });

    });

    //to crank up the providers
    beforeEach(inject());

    beforeEach(inject(function (_$rootScope_, _$controller_, _requestAuthorizationService_) {

        $rootScope = _$rootScope_;
        requestAuthorizationController = _$controller_;

        requestAuthorizationServiceMock = _requestAuthorizationService_;

    }));

     describe('requestAuthorization function', function() {

         it('RequestAuthorizationService.getDummy() is called', function() {

            $rootScope.$digest();
            expect(requestAuthorizationServiceMock.getDummy).toHaveBeenCalled();

        });

    });


});

Answer №1

After reflecting on my approach, I realize that I've overloaded the controller with too much business logic. Following guidance from this article and others, it's clear that reducing business logic in the controller and moving it to service public methods is the way to go. By adhering to this principle, private methods may become unnecessary. However, a lingering question remains about what to do with the

activate()

function. Is testing the outcome of the executed function, which populates the viewmodel, sufficient? I believe the answer is yes.

Currently, my focus is ensuring that viewmodel variables are populated correctly while refactoring business logic into services.

In my view, it's easier to write code that works but is difficult to test than it is to write code that is both functional and easy to test.

Another layer to this narrative is my background as a tester for over a decade, providing me with substantial experience in various aspects of testing. Yet now, I find myself challenged in adopting a testable/test-driven development approach to coding. :)

Answer №2

According to the guidelines set by John Papa, the activate() function serves as a way to abstract certain processes. If your constructor method has extensive setup tasks, it is recommended to move them to the activate() function for a cleaner constructor. However, in the context of your provided code, this approach may not add significant value.

It is advised to focus unit testing on APIs and external interfaces of units, such as properties and methods accessed by views within a controller.

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

Save the output to the server's file

Looking for a straightforward way to output the results of a json query in a more structured format. I currently have a file that fetches statistics from a json query and displays it using document.write. Now, I need these results to be stored in a csv or ...

What is the best way to delete an element from two arrays while keeping their original order intact?

Currently, I am faced with the task of removing 2 items from 2 separate arrays. One array contains values while the other does not, but both arrays have the same order. (I am using discord.js for this). To view the code snippet, you can visit - Unfortun ...

Trouble viewing images on Vercel deployment, yet they display correctly on localhost

Having deployed my Node application on Vercel at node-demo-ashen.vercel.app, I am facing an issue where the images are not loading on Vercel as expected. Strangely, they load perfectly fine on localhost:3000. Despite multiple configurations made to the ver ...

Sign up for your own personalized Express and HBS helper now!

As a newcomer to Node, I appreciate your patience with me. I recently utilized the express generator module with the -hbs flag to switch from the default templating engine to Handlebars. Currently, I am attempting to register a custom helper to enable me ...

Ways to determine if a string or HTML contains repeated consecutive elements

Assume I am working with the following string <div id="ch">abcdefg<img /><img />hij</div> <div id="ad">abc<img />defg<img />hij</div> strHtml = $('div#ch').html(); strHtmlFalse = $('div#ad&ap ...

Using JavaScript, generate ten clickable circles on the screen. Each circle should display the number of times it has been clicked in the center when interacted with

I am currently working on my JavaScript skills and would like to implement a fun project involving 10 colorful circles. While I can easily create these circles using HTML and CSS, I require assistance with the interactive functionality using JavaScript. ...

Transforming Sphere into Flat Surface

How can I convert the SphereGeometry() object into a flat plane on the screen? I want it to function in the same way as demonstrated on this website, where the view changes when clicking on the bottom right buttons. Below is the code for creating the sph ...

Hold on until the element becomes clickable

When attempting to logout from the menu drop down, I encountered an error stating "Unable to locate element." I suspect there may be a synchronization issue as adding a `browser.sleep(5000);` allowed the tests to pass, but this solution is not stable. ...

Is there a way in javascript, jquery, and plupload to dynamically pass a parameter to the URL without needing to reload the page or object?

I've recently taken over a project that utilizes plupload. I am encountering an issue where I need to change the URL of the object after it has been loaded. However, I'm unsure if this is possible and what steps I would need to take to achieve th ...

Hide the off-canvas menu when clicking outside

I recently created a unique "slide-out" menu which you can check out here: SASS Slide-out Menu. While it is functional, I am looking to add a feature where clicking outside of the menu will automatically close it by removing the "nav-open" class. I a ...

Spinal cord: Connecting to the surrounding container of the view

Within my Backbone IndexView, I am using a TaskView for each 'task' model. I would like to bind an event to the li element that encloses the taskview. For instance, if the 'className' attribute is 'task', I want to trigger an ...

The custom tab component in React is currently not accepting the "disabledTabs" prop

I have designed a tab component as shown below: tab/index.jsx import React from 'react'; import TabHeader from './header'; import TabBody from './body'; import TabHeaderList from './header/list'; import TabBodyList ...

How can one determine the number of steps back from the current page while browsing through the browser history?

When using the HTML5 history API, is there a way to determine the current depth in the navigation history? In other words, how many steps away am I from the latest view or the maximum number of forwards that can be done using the forward button in the brow ...

Can you explain the purpose of 'cb' in the multer module

Within the code snippet below, taken from the multer API, both the destination and filename options consist of anonymous functions. These functions contain an argument named cb. I am curious whether these callback functions are defined within the multer ...

Analyzing JavaScript code coverage through unit testing with Jenkins and SonarQube

We have successfully implemented Jenkins/SonarQube to enforce a requirement that any new code committed by developers must have at least 70% unit test code coverage for Java. However, when it comes to applying the same rule for JavaScript, we encountered s ...

Is it more advantageous to pass a value as a prop to a child component, or should the child component retrieve it from Redux instead?

In my scenario, there are two components called <Parent> and <Child>. The <Parent> component is connected to a Redux state property named prop1 using the mapStateToProps() function. Now, I also need the <Child> component to have acc ...

Tips for modifying the text of a label within a node package module

I'm in the process of developing a React web application, and I've incorporated an English node module package called react-timelines. However, I need to translate the label text "Today" into Spanish, which should be "Hoy". When I attempt to modi ...

Revise Bootstrap accordion setup

I currently have a Bootstrap accordion set up on my website using Bootstrap 4.1.0 <div id="accordion" class="mt-3"> <div class="card"> <div class="card-header bg-dark text-white" id="headingOne"> <h5 class="mb-0 f ...

How can I show the MySQL "result" object to the user using express-handlebars?

In my Node.js code, I currently have a query that looks like this: app.get('/fav/books', function(req, res){ var sql = ("SELECT title, pictureUrl, author, description, genre FROM books") connection.query(sql, function(err, result){ if(err) ...

What is the best way to ensure that a specific number of XHR callbacks have successfully completed before proceeding with further actions?

I have four requests that each have their own callback and can fire in any order. However, I need all the callbacks to finish successfully before executing mergeData. The issue with my current approach is that the initial parameter values do not refresh ...