Skip to content

test-lib: should be able to call inject AsyncTestCompleter within beforeEach but throws instead (alpha-36) #4035

Closed

Description

@wardbell

Often useful to complete test setup in the beforeEach when the setup involves BOTH injection and an async invocation of a method on the injected service. Then subsequent tests can be synchronous.

Here's an example:

  describe('#removeHero(hero)', () => {

    let service: HeroService;
    let cachedHeroes:Hero[];

    // prime the HeroService's cache asynchronously
    // notice we can use `injectAsync` in a `beforeEach` too!
    beforeEach(
      inject([AsyncTestCompleter, HeroService], (async: AsyncTestCompleter, hs:HeroService) => {
      var done = async.done.bind(async); // convenience
      service = hs;
      service.getAllHeroes()
      .then(heroes => cachedHeroes = heroes)
      .catch(fail).then(done, done)
    }));

    // the following tests can be synchronous because the tested `remove` method is synchronous
    // it presupposes that the cache of heroes has been filled by a previous `getAllHeroes` async call
   ...
});

Unfortunately, this throws:

Error: Error during instantiation of AsyncTestCompleter!.
Error: DI Exception
    at InstantiationError.BaseException (http://localhost:8080/node_modules/angular2/bundles/angular2.dev.js:7767:25)
    at InstantiationError.AbstractBindingError (http://localhost:8080/node_modules/angular2/bundles/angular2.dev.js:9117:66)
    at new InstantiationError (http://localhost:8080/node_modules/angular2/bundles/angular2.dev.js:9159:64)
... more ...

Activity

  1. vicb commented on Sep 8, 2015

    @vicb
    Contributor

    Unfortunately, it is not possible to inject the AsyncTestCompleter in a beforeEach because it depends on done which is a parameter to it (and the likes).

    However we should investigate why we don't get the expected & clearer error message.

  2. wardbell commented on Sep 8, 2015

    @wardbell
    ContributorAuthor

    Huh? done is passed into the beforeEach. I write non-Angular async beforeEach methods all the time.

  3. wardbell commented on Sep 8, 2015

    @wardbell
    ContributorAuthor

    I'm seeing the following boilerplate at the top of all my it tests ... and something similar at the top of tests throughout the Angular code base.

          it('looking for something',
            injectTcb((done, tcb) => {    // injectTcb is my helper
                tcb.overrideTemplate(SomeComponent, template)
                .createAsync(SomeComponent)
                .then((rootTC: RTC) => {
    
                    ... at last we can do something interesting ...
    
                })
               .catch(fail).then(done, done);
          }));
    

    This is just nuts!

    It should be closer to:

          let rootTC:RootTestComponent;
    
          beforeEach(injectTcb((done, tcb) => {    // injectTcb is my helper
                tcb.overrideTemplate(SomeComponent, template)
                .createAsync(SomeComponent)
                .then((rtc: RTC) => rootTC = rtc)
                .catch(fail).then(done, done);
          }));
    
          it('looking for something synchronously', ()  => {  
             ...   test something ...
          });
    
          it('looking for something async', (done:DoneFn)  => {  
            ...   test something async ...
            .catch(fail).then(done, done);
          });
    
  4. vicb commented on Sep 8, 2015

    @vicb
    Contributor

    You're actually right, done is actually a parameter to both beforeEach and it.

    However jasmine's beforeEach is used by beforeEachBindings and the beforeEach used in the unit tests is actually a test_lib construct. I'm not sure if/how we can support injecting an AsyncTestCompleter there.

  5. wardbell commented on Sep 8, 2015

    @wardbell
    ContributorAuthor

    The AsyncTestCompleter is a PITA. It's an object with a done fn. Why does it exist? DART? I never use it. In fact, my first action is let done=async.done.bind(async);. My test helpers do this for me.

  6. wardbell commented on Sep 8, 2015

    @wardbell
    ContributorAuthor

    At worst a dedicated beforeEachSomething (akin to beforeEachBinding) could do the trick. Ideally I could combine test-binding set up and TCB initialization in the same beforeEach....

    I would be happy to work w/ the person responsible for testing to come up with a more efficient API.

  7. vicb commented on Sep 8, 2015

    @vicb
    Contributor

    Why does it exist?

    To be able to inject() dependencies.

  8. wardbell commented on Sep 9, 2015

    @wardbell
    ContributorAuthor

    AsycTestCompleter is needed to inject? That is hard to fathom. I asked Igor about this thing too. He doesn't know why it exists either. We both suspect it's. A DART artifact that has no relevance to the JS developer.

    All beside the point really. The more I look at the code the more I am convinced that this is relatively easy to fix. Might have a moment to try.

    Meanwhile, I roll my own helper that creates a beforeEach like function that I then pass into the it as the test definition fn. Has the same effect as a beforeEach and DRYs out the test. I recommend this approach to others who stumble into this question.

  9. changed the title [-]test-lib: should be able to call inject within `beforeEach` but throws instead (alpha-36)[/-] [+]test-lib: should be able to call inject AsyncTestCompleter within `beforeEach` but throws instead (alpha-36)[/+] on Sep 28, 2015
  10. mhevery commented on Sep 28, 2015

    @mhevery
    Contributor

    It exists so that we have a type for injection. Otherwise how do you refer to the function

    Example:

    (done:Function) => ...; can not be injected. What is Function?
    you could write
    (@Inject(AsyncTestCompleter) done:Function) => ... that would work but at that point you may as well just do
    (done:AsyncTestCompleter) => ...

  11. wardbell commented on Sep 29, 2015

    @wardbell
    ContributorAuthor

    My first reaction is "why are we using DI to supply this value to the callback?" I don't have to do that in any of my wrapper functions. I do say that the first argument past to the callback will be the done function as we see here: injectAsync([], done => { done()})

    If I'm injecting something, well that looks like this: injectAsync([d1, d2], (done, d1, d2) => { done()})

    This works great for me and I'm not sure what the objection would be.

    But for the sake of argument, let's suppose you wanted to use DI to inject done and let the developer pick where it goes in the parameter list.

    You absolutely can represent the Jasmine done in the manner most friendly to the test developer.

    Here is a plunker that demonstrates.

    There is a trick; one must represent the done fn as if it were a class:

    export class DoneFn { } // Dummy class for binding purposes to represent Jasmine done
    

    You will know better than I whether this actually has to be exported and revealed. I'll bet you can bury it.

    Anyway, when you have the actual done function in hand, you bind it into the list of testbindings that you're already building:

    // fake of the done from Jasmine test
    function testDone() { alert('The test is done')}
    bind(DoneFn).toValue( testDone) 
    

    And now it's available as a callable function wherever you inject it. In my plunker I injected it into a component's constructor but you get the idea:

    export class App {
       constructor (car: Car, done: DoneFn){
        done();    
      }
    }
    

    All of this is quite apart from the issue itself which asks _"Why can't I have the AsyncTestCompleter .. or better yet ... the done function ... from within the beforeEach?"_

  12. vicb commented on Sep 29, 2015

    @vicb
    Contributor
    export class App {
       constructor (car: Car, done: DoneFn){
        done();    
      }
    }

    would the TS compiler be happy with that ?

    One reason to have a class is to make the Dart compiler happy. But actually you should be able to implement the Function interface:

    class DoneFn implements Function {
      call() {}
    }

    Looking forward to your PR :)

  13. 5 remaining items

  14. juliemr commented on Oct 13, 2015

    @juliemr
    Contributor

    Done with a1fa2e4

  15. angular-automatic-lock-bot commented on Sep 7, 2019

    @angular-automatic-lock-bot

    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.

  16. locked and limited conversation to collaborators on Sep 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions