What is the way to imitate a variable with Jasmine Spy?

Trying to troubleshoot a login feature, how can I mock everything except string variables?

@Component({
   selector: 'app-login',
   templateUrl: './login.component.html',
   styleUrls: ['./login.component.scss'])

   export class LoginComponent {
   username: string;
   password: string;
   loginSpinner: boolean;

   constructor(public authService: AuthService, public router: Router) {
   }

   login() {
     this.loginSpinner = true;
     this.authService.login(this.username, this.password).subscribe(() => {
     this.loginSpinner = false;
     if (this.authService.isLoggedIn) {
     // Get the redirect URL from our auth service
     // If no redirect has been set, use the default
     const redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/';

     // Redirect the user
     this.router.navigate([redirect]);
    }
  }, () => {
  // also want to hide the spinner on an error response from server when attempting login
     this.loginSpinner = false;
  });
 }

   logout() {
     this.authService.logout();
    }
}

The login component contains a service named authService. Need help mocking every aspect except for the string variable within authService known as redirect URL.

import {LoginComponent} from './login.component';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA} from '@angular/core';
import {AuthService} from './auth.service';
import {HttpClient, HttpHandler} from '@angular/common/http';
import {JwtHelperService, JwtModule, JwtModuleOptions} from '@auth0/angular-jwt';
import {TokenService} from './token.service';
import {Router} from '@angular/router';
import {Observable, of} from 'rxjs';


describe('LoginComponent', () => {
    let component: LoginComponent;
    let fixture: ComponentFixture<LoginComponent>;

    const JWT_Module_Options: JwtModuleOptions = {
        config: {
            tokenGetter: function () {
                return '';
            },
            whitelistedDomains: []
        }
    };

    const authServiceSpy = jasmine.createSpyObj('AuthService', ['login', 'isLoggedIn']);

    authServiceSpy.login.and.callFake(function () {
        return of({});
    });

    beforeEach(async(() => {

        TestBed.configureTestingModule({
            imports: [
                JwtModule.forRoot(JWT_Module_Options)
            ],
            declarations: [LoginComponent],
            providers: [HttpClient, HttpHandler, JwtHelperService, TokenService,
                [
                    {
                        provide: AuthService,
                        useValue: authServiceSpy
                    },

                    {
                        provide: Router,
                        useClass: class {
                            navigate = jasmine.createSpy('navigate');
                        }
                    }]],
            schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
        }).compileComponents();
    }));

    beforeEach(() => {


        fixture = TestBed.createComponent(LoginComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    it('should create', () => {
        expect(component).toBeTruthy();
    });

    it('should log in properly without redirect URL', () => {


        authServiceSpy.isLoggedIn.and.callFake(function () {
            return true;
        });
        component.login();

        expect(component.router.navigate).toHaveBeenCalledWith(['/']);
        expect(component.loginSpinner).toEqual(false);

    });

    it('should log in properly with redirect URL', () => {


        authServiceSpy.isLoggedIn.and.callFake(function () {
            return true;
        });


        // this doesn't work
        authServiceSpy.redirectUrl = '/foo';

        component.login();


        expect(component.router.navigate).toHaveBeenCalledWith(['/foo']);
        expect(component.loginSpinner).toEqual(false);

    });

   }
);

Answer №1

Here is a more efficient method that utilizes existing resources without the need to create additional classes. Start by defining a variable like this:

let authService: AuthService;

After setting up the testbed, assign the actual service used by the TestBed in the final beforeEach():

authService = TestBed.get(AuthService);

Then, in your test, you can modify .redirectUrl by using the following code:

// attempting to modify it incorrectly
authServiceSpy.redirectUrl = '/foo';
// correctly modifying it
authService.redirectUrl = '/foo';

See it in action on StackBlitz.

Answer №2

Give this a shot:

const spy = spyOnProperty(authServiceSpy, 'redirectUrl').and.returnValue(
  '/bar'
);
expect(authServiceSpy.redirectUrl).toBe('/bar');
expect(spy).toHaveBeenCalled();

Answer №3

Are you able to attempt this?

Develop a simulated class:

class MockAuthService{
     public redirectUrl = "/foo";

     isLoggedIn(){
         /*This can be specifically defined for spies*/
         return true;
     }
}

In your testing environment:

describe('LoginComponent', () => {
    ...
    let mockAuthService = new MockAuthService();
    ...

    beforeEach(async(() => {
       TestBed.configureTestingModule({
          imports: [...],
          declarations: [...],
          providers: [...,
            [
              {
                   provide: AuthService,
                   useValue: mockAuthService
              },
            ]],
        schemas: [...]
    }).compileComponents();
    ....
    it('should successfully log in with specified redirect URL', () => {
        mockAuthService.redirectUrl = '/foo';
        component.login();
        expect(component.router.navigate).toHaveBeenCalledWith(['/foo']);
        expect(component.loginSpinner).toEqual(false);
    });
    ...

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

Deactivate user session in LoopBack 4 API

Can anyone provide a clear example of an API endpoint for logging out that allows for deleting the token stored during login instead of relying on the web browser? It seems there is no documentation available on how LoopBack generates a default user when ...

Custom options titled MUI Palette - The property 'primary' is not found in the 'TypeBackground' type

I am currently working on expanding the MUI palette to include my own custom properties. Here is the code I have been using: declare module '@mui/material/styles' { interface Palette { border: Palette['primary'] background: Pa ...

There is a potential for an object to be 'undefined' in TypeScript

My current project involves making a GET request from a mockAPI with the following structure: "usuarios": [ { "nome": "foo bar", "cpf": "213.123.123-45", "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cf ...

Guide to generating a dropdown menu and linking it with data received from a server

I am completely new to Angular and recently received a project involving it. My task is to create a nested dropdown list for Json data retrieved from a server using Rest Api calls. The data contains a Level attribute that indicates the hierarchy level of ...

The module 'AppModule' raised an error due to an unexpected value being imported, specifically 'AngularFireDatabase'. For proper functionality, consider adding a @NgModule annotation

App.Module.ts import { AngularFireDatabase } from 'angularfire2/database'; imports: [ AngularFireDatabase ] I can't seem to figure out why it is requesting me to include a @NgModule annotation when I believe it is unnecessary. My ...

Persistent notification that just won't disappear

I've been working on improving my Notification Component in React. import React, { useEffect, useState } from "react"; import { IoMdClose } from "react-icons/io"; export type NotificationValueType = { message: string; type: & ...

Combining data types to create a unified set of keys found within a complex nested structure

This problem is really testing my patience. No matter what I do, I just can't seem to make it work properly. Here's the closest I've come so far: // Defining a complex type type O = Record<'a', Record<'b' | 'x& ...

Title: How to Build a Dynamic Logo Carousel with React and CSS without External Dependencies

Currently, I am in the process of integrating a logo carousel into my React web application using CSS. My goal is to create a slider that loops infinitely, with the last logo seamlessly transitioning to the first logo and continuing this cycle indefinitely ...

Steps to resolve the issue of being unable to destructure property temperatureData from 'undefined' or 'null' in a React application without using a class component

CODE: const { temperatureData } = state; return ( <> <div className="flex flex-row"> {temperatureData.map((item, i) => ( <div className="flex flex-auto rounded justify-center items-center te ...

Angular projects experience issues with importing date-fns which results in failing Jest tests

After updating one of my Angular projects to version 13, I encountered strange errors while running unit tests in the project. To better understand this issue, I created a simple example within a new Angular project: import { format } from 'date-fns& ...

What is the best way to capture the current state of Angular's components?

Creating an estimate application using angular-cli, I have a unique concept where users can add a dynamic number of child components. It is essential to store the state and data of these components for future edits and exports. ...

exploring alternatives to ng-container in angular-4.x for selecting elements

Currently in my Angular 4.x project, I have a component using the Selector 'abc' as shown below: @Component({ selector: "Abc", templateUrl: "Abc.html", styleUrls: [ "Abc.css" ] }) However, the "Abc" tag is also present in the DOM, b ...

Angular HttpInterceptor Unit Test: toHaveBeenCalledWith method shows no sign of being called

I have been working on unit testing an error-handling interceptor and encountered an issue with the toHaveBeenCalledWith function. It keeps giving a "but it was never called" message in the console. Can anyone shed some light on why this might be happening ...

Applying ORM Drizzle in cases of conflict

Here's where I'm currently at: If I use onConflictDoNothing, the plan is to insert a new record into the database. However, if a record with the same userId and provider already exists, and the apiKey of the existing record is not equal to the ap ...

Is there a way I can utilize the $timeout and $interval functionalities that were implemented in angular.js, but for the Angular framework?

When working with Angular.js, I made use of the $timeout and $interval functions (which are similar to setInterval and setTimeout in JavaScript). $timeout(function(){}) $interval(function(){},5000) To stop the interval, I utilized $interval.cancel($scop ...

Navigating nested data structures in reactive forms

When performing a POST request, we often create something similar to: const userData = this.userForm.value; Imagine you have the following template: <input type="text" id="userName" formControlName="userName"> <input type="email" id="userEmail" ...

The module 'AnotherModule' in a different file has unexpectedly imported a value 'Module in file'. Make sure to include a @NgModule annotation to resolve this issue

After struggling with this problem for the past four days, I've exhausted all resources on Stack Overflow and Google. Hopefully, someone here can provide some insight. I have two Angular 11 libraries - one core and the other called components. Compone ...

JavaScript's Array.map function failing to return a value

Here is a snippet of code from an api endpoint in nextJS that retrieves the corresponding authors for a given array of posts. Each post in the array contains an "authorId" key. The initial approach did not yield the expected results: const users = posts.ma ...

A special term in Typescript that consistently points to the present object within the class

Is it feasible to utilize a reference to the current instance of a class in Typescript, similar to "this" in C# and Java, rather than the typical binding context interpretation in JavaScript? ...

Guide on generating a video thumbnail using JavaScript Application

Searching for a way to easily create a thumbnail from a video for uploading alongside the video itself to a server? I've been looking for JavaScript libraries to simplify the process without much luck. The scenario involves the user selecting a video ...