Search code examples
node.jsangularjestjsangular-httpclientangular-devkit

HttpClientModule and mat-icons will not work when using ng serve (Angular 6)


I've recently added basic unit tests with jest and now for some reason when I run the angular app using "ng serve" my mat-icons show up as and my HttpClient calling my backend will not return any response. I am subscribed to the observable and even try to catch the empty observable with .pipe(finalize()), no response, and the network is not showing any requests being made. The terminal running ng serve also is not showing any errors.

I can confirm that when I build and serve the files through a node server for production(I know I should be using ngnx) everything works as expected. The back-end api is also responding correctly to postman and terminal curl calls.

I have a feeling it has to do with how I configured jest or something I messed up in a config file but I can't seem to figure it out. I also have no clue how this svg class="fake-testing-svg" is getting injected into the html as I cannot find it anywhere in my code.

login.component.ts

authenticateUser() : void {
// Retrieve core id and passsword from login form
let user_core_id = this.loginForm.value.user_core_id,
    password = this.loginForm.value.password;
console.log("calling login service......");

this.loginService.adAuth(user_core_id, password)
  .pipe(finalize(() => console.log('complete!')))
  .subscribe(res => {
    console.log("got a response from login service subscription......");
    // Check if there was an error authenticating the user in the db
    if(res.error) {
      this.alertService.error("Invalid Credentials");
      console.log("AD Authentication error: " + JSON.stringify(res));
    } 
    else {
      // User is authenticated in the AD
      // cache user_core_id, user_display name and user AD base_dn
      console.log(res.core_id + " is authenticated in the AD");
      localStorage.setItem('user_core_id', res.core_id.toUpperCase());
      localStorage.setItem('user_display_name', res.display_name );
      localStorage.setItem('user_dn', res.base);

      // Grab user data from the AD callback and send it to the db
      let user_object = res;
      // Update the user in the db
      this.getIsAdmin(user_object); 
    }
  }, 
  err => {
    // Error authenticating user against the AD
    this.alertService.error("Invalid Credentials");
    console.log("AD Auth error: " + JSON.stringify(err));
  });

login.service.ts

adAuth(user_core_id: string, password: string) : Observable<any> {
  console.log("made it to the login service adAuth() method....");

  let body = {
        user_core_id: user_core_id,
        password: password
      }

  return this.http.post<any>(`${environment.adApiUrl}/auth/user`, body);
}

server.js (should not affect ng serve)

const express = require('express'),
  bodyParser = require('body-parser'),
  path = require('path'),
  cors = require('cors'),
  fs = require('fs');

  // Set up express middleware, json parser, cors
  const app = express();
  app.use(bodyParser.json());
  app.use(cors());

  // For reading env variables
  require('dotenv').config();

  var staticRoot = __dirname + '/';

  console.log("env: " + process.env.NODE_ENV);

  if(process.env.NODE_ENV == 'production') {
    app.use(express.static(path.join(staticRoot, 'dist/toolsportal-client-build-prod')));
    app.use(function(req, res, next) {
     // if the request is not html then move along
     var accept = req.accepts('html', 'json', 'xml');
     if(accept !== 'html') {
       return next();
      }
     // if the request has a '.' assume that it's for a file, move along
     var ext = path.extname(req.path);
     if(ext !== '') {
       return next();
     }
    fs.createReadStream(staticRoot + 'dist/toolsportal-client-build-prod/index.html').pipe(res);
   });
  }

  const PORT = process.env.NODE_PORT || 3001;

  app.listen(PORT, "0.0.0.0", function () {
   console.log('SERVER RUNNING ON PORT:' + PORT)
  });

  // Kubernetes pod readiness/liveness probe
  app.use('/kube/healthy', (req, res) => {
   res.status(200).json({ healthy: true });
  });

 // Kubernetes pod readiness/liveness probe (backup)
 app.get('/', (req, res) => {
   res.status(200).json({ healthy: true });
 });

 // Catch errors
 app.on('error', (error) => {
   console.log("error: " + error);
 });

 process.on('uncaughtException', (error) => {
   console.log("uncaughtException error: " + error);
 });

angular.json

{
 "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
 "version": 1,
 "newProjectRoot": "projects",
 "projects": {
   "toolsportal-client": {
     "projectType": "application",
     "schematics": {},
     "root": "",
     "sourceRoot": "src",
     "prefix": "app",
     "architect": {
       "build": {
         "builder": "@angular-devkit/build-angular:browser",
         "options": {
           "outputPath": "src/api/dist/toolsportal-client-build-prod",
           "index": "src/index.html",
           "main": "src/main.ts",
           "polyfills": "src/polyfills.ts",
           "tsConfig": "tsconfig.app.json",
           "aot": true,
           "assets": [
             "src/favicon.ico",
             "src/assets"
           ],
           "styles": [
             "node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
             "src/styles.css",
             "node_modules/bootstrap/dist/css/bootstrap.min.css"
           ],
           "scripts": [
             "node_modules/jquery/dist/jquery.min.js",
             "node_modules/popper.js/dist/umd/popper.min.js",
             "node_modules/bootstrap/dist/js/bootstrap.js",
             "node_modules/hammerjs/hammer.min.js"
           ]
          },
          "configurations": {
            "qa": {
              "fileReplacements": [
              {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.qa.ts"
              }
             ]
           },
           "dev": {
             "fileReplacements": [
               {
                 "replace": "src/environments/environment.ts",
                 "with": "src/environments/environment.dev.ts"
               }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": false,
              "budgets": [
               {
                 "type": "initial",
                 "maximumWarning": "2mb",
                 "maximumError": "5mb"
               },
               {
                "type": "anyComponentStyle",
                "maximumWarning": "6kb",
                "maximumError": "10kb"
               }
              ]
             }
            }
          },
          "serve": {
            "builder": "@angular-devkit/build-angular:dev-server",
            "options": {
                "browserTarget": "toolsportal-client:build"
              },
            "configurations": {
             "production": {
               "browserTarget": "toolsportal-client:build:production"
              },
              "dev": {
               "browserTarget": "toolsportal-client:build:dev"
               },
              "qa": {
                "browserTarget": "toolsportal-client:build:qa"
               }
              }
            },
           "extract-i18n": {
             "builder": "@angular-devkit/build-angular:extract-i18n",
             "options": {
               "browserTarget": "toolsportal-client:build"
              }
             },
            "lint": {
              "builder": "@angular-devkit/build-angular:tslint",
              "options": {
               "tsConfig": [
                "tsconfig.app.json",
                "tsconfig.spec.json",
                "e2e/tsconfig.json"
                ],
               "exclude": [
                  "**/node_modules/**"
                 ]
               }
             },
             "e2e": {
               "builder": "@angular-devkit/build-angular:protractor",
               "options": {
                 "protractorConfig": "e2e/protractor.conf.js",
                 "devServerTarget": "toolsportal-client:serve"
                },
             "configurations": {
              "production": {
               "devServerTarget": "toolsportal-client:serve:production"
               }
             }
            }
           }
          }
         },
         "defaultProject": "toolsportal-client",
         "cli": {
           "analytics": "0db305fd-b474-49d5-a4ec-95ceec1c2bcf"
          }
         }

jest.config.js

const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig');

module.exports = {
  preset: 'jest-preset-angular',
  roots: ['<rootDir>/src/'],
  testMatch: ['**/+(*.)+(spec).+(ts)'],
  setupFilesAfterEnv: ['<rootDir>/src/test.ts'],
  collectCoverage: true,
  coverageReporters: ['html'],
  coverageDirectory: 'coverage/my-app',
  moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths || {}, {
   prefix: '<rootDir>/'
  })
 };

tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2015",
    "lib": [
      "es2018",
      "dom"
     ],
    "types": [
      "jquery",
      "bootstrap",  
      "node",
     "jest"
    ]
   },
   "angularCompilerOptions": {
   "fullTemplateTypeCheck": true,
   "strictInjectionParameters": true
   }
  }

tsconfig.app.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": []
   },
   "files": [
     "src/main.ts",
     "src/polyfills.ts"
    ],  
   "include": [
    "src/**/*.d.ts"
    ],
    "angularCompilerOptions": {
      "enableIvy": false
     }
    }

package.json

{
  "name": "toolsportal-client",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "serve-dev": "ng serve -c=dev --port=4201",
    "serve-qa": "ng serve -c=qa --port=4202",
    "start-node-log": "node src/api/server.js > src/api/app.log 2>&1",
    "start-node": "node src/api/server.js",
    "start-nodemon": "nodemon src/api/server.js",
    "build": "ng build",
    "build-prod": "ng build --prod",
    "test": "jest",
    "lint": "ng lint",
    "e2e": "ng e2e"
   },
   "private": true,
   "dependencies": {
     "@angular/animations": "^9.1.12",
     "@angular/cdk": "^9.2.4",
     "@angular/common": "^9.1.12",
     "@angular/compiler": "^9.1.12",
     "@angular/core": "^9.1.12",
     "@angular/forms": "^9.1.12",
     "@angular/localize": "^9.1.12",
     "@angular/material": "^9.2.4",
     "@angular/platform-browser": "^9.1.12",
     "@angular/platform-browser-dynamic": "^9.1.12",
     "@angular/router": "^9.1.12",
     "@kolkov/angular-editor": "^1.1.2",
     "@ng-bootstrap/ng-bootstrap": "^6.2.0",
     "@types/bootstrap": "^4.5.0",
     "@types/jquery": "^3.5.0",
     "body-parser": "^1.19.0",
     "bootstrap": "^4.5.0",
     "cors": "^2.8.5",
     "dotenv": "^8.2.0",
     "express": "^4.17.1",
     "hammerjs": "^2.0.8",
     "jira-connector": "^3.1.0",
     "jquery": "^3.4.1",
     "jsonwebtoken": "^8.5.1",
     "jwt-decode": "^2.2.0",
     "ldapjs": "^1.0.2",
     "mongoose": "^5.9.24",
     "node-rest-client": "^3.1.0",
     "popper": "^1.0.1",
     "popper.js": "^1.16.0",
     "rxjs": "~6.5.5",
     "rxjs-compat": "^6.6.0",
     "tslib": "^1.13.0",
     "zone.js": "~0.10.3"
    },
    "devDependencies": {
      "@angular-devkit/build-angular": "^0.901.11",
      "@angular/cli": "^9.1.11",
      "@angular/compiler-cli": "^9.1.12",
      "@angular/language-service": "^9.1.12",
      "@types/jasmine": "^3.5.11",
      "@types/jasminewd2": "^2.0.8",
      "@types/jest": "^25.2.3",
      "@types/node": "^13.13.14",
      "codelyzer": "^5.2.2",
      "jasmine-core": "~3.5.0",
      "jasmine-spec-reporter": "~5.0.1",
      "jest": "^26.1.0",
      "jest-preset-angular": "^8.2.1",
      "nodemon": "^2.0.4",
      "protractor": "~5.4.3",
      "ts-node": "~8.8.2",
      "tslint": "~6.1.1",
      "typescript": "~3.8.3"
     }
    }

network

login html inspect (see the mat-icon)

terminal


Solution

  • It turns out it was in app.module.ts

    import { HttpClientModule } from '@angular/common/http';
    import { HttpClientTestingModule, HttpTestingController } from'@angular/common/http/testing';
    

    to

    import { HttpClientModule } from '@angular/common/http';
    

    Apparently importing the HttpClientTestingModule in to the app.module.ts was injecting fake svg-icons and intercepting http requests. I directly imported the HttpCientTesting Module into each component testing file and everything worked fine.