Challenge with AngularJS/Karma/Jasmine: Unable to receive value from service call

Seeking assistance with calling a Github API using AngularJS 1.5.3 and a service injected into a component.

In my unit test, I am struggling to receive a value back (although the function works in the browser). Any guidance on what might be causing this issue would be greatly appreciated.

Error Message: https://i.sstatic.net/QbgO6.png

main.component.js

(function(){
    angular.module("app").component("mainComponent", {
        templateUrl: "/templates/main.component.html",
        controllerAs: "vm",
        controller: function(APIFactory, UserFactory, $state){
            const vm = this;

            vm.searchGithub = function(){
                APIFactory.getAPI(vm.searchText).then(function(res){
                    res.status !== 200 ? $state.go("404", {errorData: res.data }) : (
                        vm.User = new UserFactory.User(res.data),
                        $state.go("profile", {userData: vm.User})
                    );
                })
                .catch(function(err){
                    $state.go("fourOFour");
                });
            };
        }
    });
})();

main.component.spec.js

describe("Main Component", function(){
    var mainComponent, APIFactory, UserFactory, $httpBackend, $q, $state, $rootScope;

    const addy = "https://api.github.com/users/";

    beforeEach(angular.mock.module("app"));

    beforeEach(inject(function(_APIFactory_, _UserFactory_, _$httpBackend_, _$state_, _$q_, _$rootScope_, _$componentController_){
        APIFactory = _APIFactory_;
        UserFactory = _UserFactory_;
        $httpBackend = _$httpBackend_;
        $state = _$state_;
        $q = _$q_;
        $rootScope = _$rootScope_;
        $rootScope.$new();
        mainComponent = _$componentController_("mainComponent", { $scope : {} });
    }));

    describe("Checking if the searchGithub() worked correctly", function(){
        var result;

        beforeEach(function(){
            spyOn(mainComponent, "searchGithub").and.callThrough();
            spyOn(APIFactory, "getAPI").and.callThrough();
            result = {};
        });

        it("should make a call to UserFactory", function(){
            mainComponent.searchText = "someName";
            expect(mainComponent.searchText).toBeDefined();

            // RESPONSE_SUCCESS does exist, I've omitted it.
            $httpBackend.whenGET(addy + mainComponent.searchText).respond(200, $q.when(RESPONSE_SUCCESS));

            // This is where I expect something to work

            APIFactory.getAPI(mainComponent.searchText).then(function(res){
                result = res;
            });

            $httpBackend.flush();

            expect(APIFactory.getAPI).toHaveBeenCalledWith(mainComponent.searchText);
            expect(mainComponent.User).toBeDefined();
        });
    });


});

Answer №1

After considering various options, I have come up with a solution. However, I am open to any suggestions for improvement.

To start, I created two mock objects and inserted them into the mainComponent. Additionally, I set up a spy for the mocked APIFactoryMock.getAPI function:

const APIFactoryMock = {
    getAPI: function(){}
};

const UserFactoryMock = {
    User: function(data){
        return {
            // properties...
        }
    }
};

beforeEach(inject(function(_APIFactory_, _UserFactory_, _$httpBackend_, _$state_, _$q_, _$rootScope_, _$componentController_){
    // setup before each test
}));

Subsequently, I proceeded with writing tests for the mock objects:

it("should make a call to UserFactory", function(){
        // test case implementation

        expect(mainComponent.searchText).toBeDefined();

        mainComponent.searchGithub(mainComponent.searchText);

        // more assertions...

    });

Answer №2

Within the response provided, you are manually initiating a call to UserFactoryMock.User in the test scenario to generate a user object.

To ensure accurate functionality testing, it is recommended to validate if UserFactory.User is invoked when calling APIFactory.getAPI and achieving success, without the need for manual invocation of UserFactory.User within the test case.

It is suggested to adjust your test case similar to the following:

describe("Main Component", function(){
var mainComponent, APIFactory, UserFactory, $httpBackend, $q, $state, $rootScope;

const url = "https://api.github.com/users/";

beforeEach(angular.mock.module("app"));

beforeEach(inject(function(_APIFactory_, _UserFactory_, _$httpBackend_, _$state_, _$q_, _$rootScope_, _$componentController_){
    APIFactory = _APIFactory_;
    UserFactory = _UserFactory_;
    $httpBackend = _$httpBackend_;
    $state = _$state_;
    $q = _$q_;
    $rootScope = _$rootScope_;
    var scope = $rootScope.$new();
    var bindings = { APIFactory: APIFactory, UserFactory: UserFactory, $state: $state };
    mainComponent = _$componentController_("mainComponent", { $scope : scope }, bindings);
}));

describe("Validation of searchGithub() functionality", function(){
    var result;

    beforeEach(function(){
        spyOn(mainComponent, "searchGithub").and.callThrough();
        spyOn(APIFactory, "getAPI").and.callFake(function() {
            var def = $q.defer();
            def.resolve(RESPONSE_SUCCESS);
            return def.promise;
        });
        spyOn(UserFactory, "User").and.callFake(function() {
            var user = { id: 666, .... };
            return user;
        });
    });

    it("should trigger a call to UserFactory", function(){
        mainComponent.searchText = "someName";
        $rootScope.$apply();
        expect(mainComponent.searchText).toBeDefined();

        mainComponent.searchGithub(); // Mimic code execution.

        $rootScope.$apply();

        //No direct calls to 'UserFactory.User' or 'APIFactory.getAPI'. Successful resolution of 'APIFactory.getAPI' triggers 'UserFactory.User' call as expected
        expect(APIFactory.getAPI).toHaveBeenCalledWith(mainComponent.searchText);
        expect(UserFactory.User).toHaveBeenCalledWith(RESPONSE_SUCCESS.data);
        expect(mainComponent.User).toBeDefined();
        expect(mainComponent.User.id).toEqual(666);
    });
});


});

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

What is the best way to bypass routerlink usage in bootstrap tabs when working on an Angular application?

Attempting to configure basic HTML bootstrap tabs: <ul class="nav nav-tabs" id="myTab" role="tablist"> <li class="nav-item"> <a class="nav-link active" id="home-tab" dat ...

Controller for Laravel Excel Download

I developed a PHP Controller to manage the export of data that is sent by JavaScript. However, even though I can see some output in the console, the file download never actually starts. I attempted to use ->store (laravel excel) and kept the file in an ...

Interactive Google Maps using Autocomplete Search Bar

How can I create a dynamic Google map based on Autocomplete Input? Here is the code that I have written: <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDeAtURNzEX26_mLTUlFXYEWW11ZdlYECM&libraries=places&language=en"></scri ...

Converting the return value data type in JavaScript

In my function countdown(n), I am trying to recursively push a value if it is greater than 0. However, I am facing an issue with the .push() function not being recognized because the return value of the function is an unknown type. Is there a way in Java ...

Which is the better option for selecting DOM elements in a Vuejs 3 application: using raw js or jquery?

 I am currently working on developing an application using Node.js and Vue.js 3. One of the features I have implemented is a sidebar that dynamically fetches links from a routes file and displays them. The sidebar consists of a component that organize ...

What could be the reason for jQuery not functioning properly as needed?

function toggleDisplayingRooms(nameSelect){ if(nameSelect){ firstroom = document.getElementById("firstroom").value; secondroom = document.getElementById("secondroom").value; thirdroom = ...

Open a new window, but the 'To' field address is set to mailto:[email protected]

In my JavaScript code, I have a functionality to open a mail client in a new window for sending an email: <a onClick="javascript:window.open('mailto:<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a1d4d2c4d3e1d5c4d2d58f ...

AngularJS ng-repeat: A guide to simultaneously looping through an object's array of properties and an array of objects

Here's a question that I have: I currently have an array of objects in my scope that looks like this: Object[0] { configured: true, configuration: { Object[0] { qty1: 1, qty2: 2 } Object[1] { qty1: 3, ...

After making a POST request, the `Req.body` is assigned to

This is the JavaScript code I am using: app.use(express.static(__dirname)); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); // support json encoded bodies app.get('/', function(req, res){ res.sendFile(__dirn ...

What's the best grid to use with Angular JS?

What is the top choice for displaying items in a table format using AngularJS? I have come across several options such as ng-table, ui-grid, ag-grid, and kendo-grid. I require functionalities like sorting, filtering, row styling, header column splitting, ...

Error: The image preview feature is malfunctioning in the Javascript code

Let me start by explaining the code. There are two views where I create a preview of an image from an input file. The first view contains a form for creating a project: <table> <tr> <td> <img src="" ...

Click to add a card

Attempting to incorporate a new row with the click of a button dynamically has proven to be challenging for me. As someone who is relatively new to JavaScript, I am struggling to figure it out. In the demonstration provided below, you will notice that noth ...

Retrieve and process information retrieved from an Ajax call in ASP.NET using AJAX

When I receive a list of data from an Ajax call, it looks like this. $(document).ready(function () { var hashtag = 'dilwale' var accessToken = '16741082.1b07669.121a338d0cbe4ff6a5e04543158a4f82' $.ajax({ url: ' ...

integrating an array into MongoDB using Angular and NodeJS

Encountering an issue with mlab (mongoose), angular.js, HTML, and JavaScript. When passing an array with values from an angular.js controller to the server side (node.js), it fails to insert the data into the schema in mlab. Below is my HTML code: <fo ...

Implementing dynamic props in Vue2 component by passing arbitrary named variables

Having recently delved into Vue, I am facing a challenge that has left me scratching my head after consulting the documentation: I am struggling to pass an arbitrarily named variable as a prop to a component instance. As per my understanding, props serve ...

Is it possible to detect the source of a digest cycle in AngularJS?

I've found myself knee-deep in a legacy AngularJS project lately. The codebase is quite intricate and expansive, making it difficult to showcase here. However, I've come across an issue where functions triggered during digest changes are firing h ...

Custom headers in XmlHttpRequest: Access control check failed for preflight response

Encountering an issue with an ajax GET Request on REST Server. Test results and details provided below. There are two methods in the REST Server: 1) resource_new_get (returns json data without custom header) 2) resource_api_new_get (also returns json d ...

What is the best way to reset react-id-swiper every time an event handler is triggered in a React application?

I have incorporated the react-id-swiper module into my React project to create a dynamic image slider. By setting onClick event handlers on buttons with different id attributes, I trigger API calls that update the state and populate the ImageSlider compone ...

Error: Unable to locate module: 'material-ui/styles/colors'

I encountered an issue with the code below, as it failed to compile: import React from 'react'; import { AppBar, Toolbar } from 'material-ui'; import { Typography } from 'material-ui'; import { MuiThemeProvider, createMuiThem ...

How can one hand over a file to the http.write method in Node?

When I attempt to print a file using the res.write() method, I encounter an error: TypeError: First argument must be a string or Buffer This is my code snippet: var fs = require("fs"); var http = require("http"); http.createServer(function (req, res){ ...