Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error: Cannot make XHRs from within a fake async test. #8280

Closed
andrashatvani opened this issue Apr 27, 2016 · 21 comments
Closed

Error: Cannot make XHRs from within a fake async test. #8280

andrashatvani opened this issue Apr 27, 2016 · 21 comments
Assignees
Labels
area: testing Issues related to Angular testing features, such as TestBed

Comments

@andrashatvani
Copy link

Steps to reproduce and a minimal demo of the problem

What steps should we try in your demo to see the problem?

export function setupComponentFakeAsync(type:any, continuation:Function) {
    return fakeAsync(inject([TestComponentBuilder], (tcb:TestComponentBuilder) => {
        let fixture:ComponentFixture = null;
        tcb.createAsync(type).then((f:ComponentFixture) => fixture = f);
        tick(50);
        fixture.detectChanges();
        continuation(fixture);
    }));
}


it('shows an empty origin station text field in the default component state',
    setupComponentFakeAsync(SearchComponent, (fixture:ComponentFixture) => {
        let input:any = fixture.nativeElement.querySelector('#fromStation');

        expect(input.value).toBe('');
    })
);



**Current behavior**
Exception:

> Error: Cannot make XHRs from within a fake async test. in d:/Users/l026291/Development/ts-automat-fe/node_modules/zone.js/dist/fake-async-test.js (line 248)
    onScheduleTask@d:/Users/l026291/Development/ts-automat-fe/node_modules/zone.js/dist/fake-async-test.js:248:96
    scheduleTask@d:/Users/l026291/Development/ts-automat-fe/node_modules/angular2/bundles/angular2-polyfills.js:359:64
    scheduleMacroTask@d:/Users/l026291/Development/ts-automat-fe/node_modules/angular2/bundles/angular2-polyfills.js:299:52
    d:/Users/l026291/Development/ts-automat-fe/node_modules/angular2/bundles/angular2-polyfills.js:148:39
    send

**Expected/desired behavior**
The test is working just as with beta 15.

**Other information**
@aprinaresh97
Copy link

We are also running into the same issue. Is there a solution for this. All our unittests are failing because of this issue. I assume the XHR is used to load the styleUrl. Is there a workaround for this. Due to this issue, we are not able to close any story because of failure to meet the acceptance criteria.

Let us know how to proceed.

@aprinaresh97
Copy link

To make some of the tests passing, I had to remove the fakeAsync and skip tests that uses tick (as tick works only in the fakeAsyncZone).

What are our options? Is this going to be fixed soon?

@ghost
Copy link

ghost commented May 22, 2016

Same here. If I use async to inject the component karma fails. If I use fakeAsync I get this exception.

@onlyann
Copy link

onlyann commented May 25, 2016

This issue is also preventing us from unit testing any component that makes uses of setTimeout or the delay Rxjs operator.

@roni-frantchi
Copy link

+1 here...

@yjaaidi
Copy link
Contributor

yjaaidi commented Jun 17, 2016

+1

@yjaaidi
Copy link
Contributor

yjaaidi commented Jun 17, 2016

I guess I know where it comes from!
Are you guys using templateUrl ?
Because I have mocked the Http service and still got the error until I replaced templateUrl with template.

We should find a way to override all component templates.

@zoechi
Copy link
Contributor

zoechi commented Jun 20, 2016

@yjaaidi what Angular2 version?

@yjaaidi
Copy link
Contributor

yjaaidi commented Jun 21, 2016

Hi @zoechi,

It's rc1. Didn't try with rc2 yet.

@juliemr
Copy link
Member

juliemr commented Jun 21, 2016

This is working as intended - we can't "fake" async behavior in any reasonable way if a real XHR is made, so the test must throw an error. templateUrl and styleUrl make XHRs.

Something that we can do, however, is run tests against a cached version of the templates and styles, so we don't need to make the XHR in the first place. @vikerman worked on a system to do this, see #7940

There is little documentation though - we should add an example of a test setup using this. @vikerman does one exist that I don't know about, or should I create one?

@vicb
Copy link
Contributor

vicb commented Jun 21, 2016

Should that be in a cookbook on angular.io ? If you think this makes sense could you please create an issue with the detailson the angular.io repo. Thanks.

@pkozlowski-opensource pkozlowski-opensource added area: testing Issues related to Angular testing features, such as TestBed hotlist: example tests labels Jun 21, 2016
@centaure
Copy link

centaure commented Jun 23, 2016

Below is a spec example for a component using templateUrl (adapted from here). I used rc3 for my impl.

However, the templateUrl is being mapped to its content string within setTemplateCache below (i.e. content of ./application.html is still being inlined).
Is there a straightforward way to read from the actual ./application.html file in this example (Note that I am using systemjs for loading modules)?

 import { 
   ComponentFixture, TestComponentBuilder
 } from '@angular/compiler/testing';
 import { Component } from '@angular/core';
 import {
   beforeEachProviders,
   describe,
   expect,
   fakeAsync,
   inject,
   it,
   tick
 } from '@angular/core/testing';
 import { UrlResolver, XHR } from '@angular/compiler';
 import { 
   CachedXHR
 } from '@angular/platform-browser-dynamic/src/xhr/xhr_cache';

 describe('ApplicationComponent fixture', () => {
   function createCachedXHR(): CachedXHR {
     setTemplateCache({ './application.html': '<div>Hello World</div>' });
     return new CachedXHR();
   }
   beforeEachProviders(() => {
     return [
       { provide: UrlResolver, useClass: TestUrlResolver },
       { provide: XHR, useFactory: createCachedXHR }
     ];
   });

   it ('should have the correct content',
     fakeAsync(inject([TestComponentBuilder],
       (tcb: TestComponentBuilder) => {
         let fixture: ComponentFixture;

         tcb.createAsync(ApplicationComponent).then(
           (f) => { fixture = f; }
         );
         tick();
         expect(
           fixture.debugElement.children[0].nativeElement
         ).toHaveText('Hello World');
       }
     ))
   );
 });

 export function setTemplateCache(cache): void {
   (<any>window).$templateCache = cache;
 }

 class TestUrlResolver extends UrlResolver {
   resolve(baseUrl: string, url: string): string {
     // Don't use baseUrl to get the same URL as templateUrl.
     // This is to remove any difference between Dart and TS tests.
     return url;
   }
 }

 @Component({
   selector: 'app',
   templateUrl: './application.html'
 })
 export default class ApplicationComponent {}

@simonbear89
Copy link

@centaure Here's how I hacked my way through this:

  • Serve your .html and .css files. I'm using Karma so in my karma.conf.js I put:
files: [
  ...
  {
    pattern: 'public/templates/**/*.html',
    included: false,
    served: true
  },
  {
    pattern: 'public/css/**/*.css',
    included: false,
    served: true
  },
  ...
]
  1. Define window.$templateCache = {} at the top.
  2. Populate the template cache:
// load your test files
}).then(function () {
  ...

// GET .html and .css templates and cache them
}).then(function () {

  return Promise.all(
    Object.keys(window.__karma__.files)

    .filter(function (filename) {
      var isTemplate = filename.startsWith('/base/public/templates') && filename.endsWith('.html');
      var isCss = filename.startsWith('/base/public/css') && filename.endsWith('.css');
      return isTemplate || isCss;
    })

    .map(function (filename) {
      return new Promise((resolve, reject) => {
        $.ajax({
          type: 'GET',
          url: filename,
          dataType: 'text',
          success: function (contents) {
            filename = filename.replace('/base/public/', '');
            window.$templateCache[filename] = contents;
            resolve();
          }
        });
      });
    })
  );

// start karma
}).then(function () {
  ...
});

As you can see, I make a GET request for the contents of the files I want and just save them in the window.$templateCache object, which will be loaded later by CachedXHR.

@gridcellcoder
Copy link

@juliemr @vikerman do you guys have an example of using the cache to load template url contents?

@mattxo
Copy link

mattxo commented Nov 17, 2016

Hm, same problem here...

    it('should show already exists message when save button clicked', fakeAsync(() => 
    {                     
        Promise.all
        ([
            page.sendInput(page.firstNameInput, 'Matt'),
            page.sendInput(page.lastNameInput, 'Sample'),
            page.sendInput(page.emailInput, 'me@hotmail.com'),
            page.sendInput(page.passwordInput, 'abcDEF123'),
        ])
        .then(() => 
        {            
            page.form.submit();                                    
            tick();
            expect(component.errorMessage).toContain('already exists');                                                                                                                                
        })         
    }));

Where page.form.submit() simply calls register() and users.register() is hitting a local unit test database...

    register(event : any)
    {                        
        event.preventDefault();

        return this.users.register(this.model)            
            .then(user => this.onSuccess(user))
            .catch(error => this.onError(error));        
    }

@yjaaidi
Copy link
Contributor

yjaaidi commented Nov 17, 2016

Hello @mattxo ,

Oh! That's normal. As your app is making an http request, fakeAsync can't fake http.

You should mock the http backend. It's not as fun as AngularJS 1.x $httpBackend but here's a batteries included pattern.

    beforeEach((done) => {

        TestBed
            .configureTestingModule({
                imports: [
                    UserModule
                ],
                providers: [
                    MockBackend,
                    {
                        provide: Http,
                        deps: [MockBackend, RequestOptions],
                        useFactory: (mockBackend, requestOptions) => {
                            return new Http(mockBackend, requestOptions);
                        }
                    }
                ]
            })
            .compileComponents()
            .then(done);

    });

    beforeEach(inject([MockBackend], (mockBackend: MockBackend) => {

        this.spyConnection = jasmine.createSpy('connection');

        mockBackend.connections.subscribe((connection: MockConnection) => {
            connection.mockRespond(new Response(new ResponseOptions(this.spyConnection({
                body: connection.request.text(),
                method: connection.request.method,
                url: connection.request.url
            }))));
        });

    }));

    it(..., () => {
        this.spyConnection.and.returnValue({
            body: { /* YOUR EXPECTED RESPONSE HERE */ },
            status: 200
        });

        expect(this.spyConnection).toHaveBeenCalledTimes(1);
        expect(this.spyConnection.calls.argsFor(0)[0].method).toEqual(RequestMethod.Get);
        expect(this.spyConnection.calls.argsFor(0)[0].url).toEqual('/api/v1/endpoint.../');

    });

@mattxo
Copy link

mattxo commented Nov 18, 2016

Hey @yjaaidi,

Thanks for the sample code :)

I'd prefer not mock the backend in this case though, if I can help it. It's an e2e/integration test, and performance isn't an issue with a local database.

The fakeAsync in this case is only there to make this work

form.submit();
tick(); // wait until all promises resolve

What I'd really like, if it's not possible to have the XHR run synchronously when called under fakeAsync, is to have the register() promise returned from form.submit() or possibly better still, from page.submitButton.click(), but that doesn't seem possible either...

@Olgagr
Copy link

Olgagr commented Oct 26, 2017

We had the same error. For us the key thing was setup providers correctly in TestBed.configureTestingModule.

let lastConnection: any;
let component: InvoiceFormComponent;

beforeEach(() => {
TestBed.configureTestingModule({
    imports: [...],
    providers: [
        {
          provide: ConnectionBackend,
          useClass: MockBackend,
        },
        {
          provide: RequestOptions,
          useClass: BaseRequestOptions,
        },
        Http, // DON'T FORGET TO ADD THIS! 
    ],
    declarations: [MyComponent],
}).compileComponents();
    let backend = TestBed.get(ConnectionBackend) as MockBackend;
    backend.connections.subscribe((connection: any) => { lastConnection = connection; });
));

Then in the test:

it('getWorkItemsOptions', fakeAsync(() => {
    let options;
    componentFixture = TestBed.createComponent(MyComponent);
    component = componentFixture.componentInstance;

    component.someAsyncAction().subscribe((result) => {
        options = result;
    });

   lastConnection.mockRespond(new Response(new ResponseOptions({
       body: JSON.stringify([{ name: 'one' }, { name: 'two' }]),
       status: 200,
    })));
   tick();
   expect(options.length).toEqual(2);
}));

@vikerman
Copy link
Contributor

Example should be covered as part of resolving #12409

@Sampath-Lokuge
Copy link

I have the same error. Can I have any help here? Thanks in advance.

https://stackoverflow.com/questions/52013054/angular-6-error-cannot-make-xhrs-from-within-a-fake-async-test-request-url-ht

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: testing Issues related to Angular testing features, such as TestBed
Projects
None yet
Development

No branches or pull requests