Angular Accept.js breaking unit tests 'Reference Error: Accept is not defined'

607 Views Asked by At

I have an angular component that uses Authorize.nets Accept.js SDK to complete a payment. I don't want to load the Accept.js script unless the user is on the payment screen so I add the script to the page via a method I call on onInit in the component.

some.component.ts

ngOnInit {
  this.addAcceptJsScript();
}

private addAcceptJsScript(): void {
  const element = document.createElement('script');
  element.src = environment.acceptJsUrl;
  element.type = 'text/javascript';
  document.getElementsByTagName('head')[0].appendChild(element);
}

some.service.ts

declare var Accept: any;

@Injectable()
export class SomeService {
  private sendPaymentToAuthNet(paymentPayload): void {
    Accept.dispatchData(paymentPayload, this.handleAuthNetResponse);
  }
}

This works fine for the users as the script is loaded in the DOM and available when the component calls the service that uses the Accept API

However my unit test in the service that uses the API doesn't have any reference to what Accept is so when it tries to test the line of code where I call Accept.dispatchData(...) the test errors out saying 'Reference Error: Accept is not defined".

I have tried various ways of mocking Accept but none of them make it available to the test. How do I go about injecting my mock Accept into the service so that it's declared when the code tries to use it?

2

There are 2 best solutions below

0
Estus Flask On

Problems with testing usually indicate flaws in application design.

document preferably should be a provider, because it is supposed to be mocked at some point to avoid adding script elements to real DOM. There is DOCUMENT provider already that can be injected into the component instead of document global. And be mocked in test bed:

{
  provide: DOCUMENT,
  useValue: jasmine.createSpyObj('document', ['createElement', 'getElementsByTagName'])
}

Where createElement and getElementsByTagName Jasmine spies should be configured to return proper mock objects.

Accept should be made a provider for testability reasons as well. The value can be assigned to window['Accept'] as shown in this answer and be mocked in test bed.

3
efarley On

I just needed to include the path for the Accept.js CDN in my files object in the karma.config

files: [
  { pattern: './src/test.ts', watched: false },
  { pattern: './node_modules/@angular/material/prebuilt-themes/indigo-pink.css', included: true, watched: true },
  { pattern: 'https://jstest.authorize.net/v1/Accept.js', nonull: true }
]