ngParty / ng-metadata

Angular 2 decorators and utils for Angular 1.x
https://hotell.gitbooks.io/ng-metadata/content/
MIT License
355 stars 46 forks source link

backport angular 2 unit testing capabilities #93

Open Hotell opened 8 years ago

Hotell commented 8 years ago

currently we provide renderFactory which leverages angular 1 testing patterns.

We need to do better and support https://angular.io/docs/ts/latest/api/#!?apiFilter=core%2Ftesting

aciccarello commented 8 years ago

Do you have a list of the specific testing APIs you'd like to implement? The inject and beforeEachProviders functions looks like good ones to start with.

Also, your docs link is broken :cry:. Here's all of the testing APIs: https://angular.io/docs/ts/latest/api/#!?apiFilter=testing

Hotell commented 8 years ago

yes, those two are the primary ones I guess. In the meantime you can do something similar with little utility method https://hotell.gitbooks.io/ng-metadata/content/docs/api/testing/function.html see that queryByDirective at the end.

What link is broken? Anyway if you found something broken, file an issue or even better submit a PR. It doesn't hurt ;)

aciccarello commented 8 years ago

Good to know :smiley: . And the docs link is the one in this issue to the Angular Docs.

Hotell commented 8 years ago

oh thanks for pointing that out, updated!

Hotell commented 8 years ago

So it looks like I've made a very good decision to move this to 2.1 because ng2 RC.4 was just released with another set of breaking changes https://github.com/angular/angular/blob/master/CHANGELOG.md#200-rc4-2016-06-30

aciccarello commented 8 years ago

Good call. Overall, I'm glad to see the API not being so tightly bound to jasmine.

aciccarello commented 8 years ago

Was thinking about this today. Since RC5 brought more breaking changes to testing, here's what I see as the top testing features to implement.

@angular/core/testing

Some of the methods of TestBed refer to the RC5 modules but the implementation could be routed to angular.mock.module and be updated when #126 is implemented. There are a lot of methods/properties but we'd only need the main ones.

interface TestBed {
  configureTestingModule: (moduleDef: { providers?: any[]; declarations?: any[]; imports?: any[]; }) => void;
  createComponent: (component: T) => ComponentFixture;
  get: (token: T) => T;
}

Here's the ng-metadata interface I imagine for ComponentFixture. I may be missing some ways that the ChangeDetectionStrategy feature could be used to add additional functionality (see commented out methods from ng2).

interface ComponentFixture {
  componentInstance : T;
  nativeElement : HTMLElement; // root DOM element from elementRef
  elementRef : angular.IAugmentedJQuery;
  // debugElement : DebugElement
  // componentRef : ComponentRef<T>;
  $scope: angular.IScope; // instead of ngZone
  detectChanges: () => void; // i.e. $scope.$apply()
  destroy: () => void; // i.e. $scope.$destroy()
  // changeDetectorRef : ChangeDetectorRef;
  // checkNoChanges() : void;
  // autoDetectChanges(autoDetect?: boolean);
  // isStable() : boolean;
  // whenStable() : Promise<any>;
}
aciccarello commented 8 years ago

I've implemented a POC for this. I setup a separate project b/c it uses angular-mocks as a dependency. I'm not sure how it could be integrated. Are there plans to move to multiple scoped packages like ng2? I don't think it's really necessary but it would allow having different dependencies for the testing module.

https://github.com/aciccarello/ng-metadata-testing

Hotell commented 8 years ago

Hey @aciccarello. I took look at your code. Looks good so far.

I'm not very keen to create separate scoped packages like ng2 does(or more simplified, don't have time for that :D), just namespaced modules should be sufficient. Anyway it doesn't make much sense for ng1 IMHO.

We can leverage your work when first ng-metadata@3.beta-0 is released which should be today or tomorrow ( which will include NgModule support and so it will be aligned with ng2 stable )

aciccarello commented 8 years ago

Thanks for taking the time to take a look at it. That all makes sense. I'll work on integrating NgModule support. This is my first major OS contribution so I apologize if I make more work for you. Feel free to correct me if necessary.

JamesHenry commented 8 years ago

Yeah, thanks @aciccarello! I would say it is definitely fair to assume that all Angular 1 projects have angular-mocks as a peerDependency already. If they don't, they have bigger problems than ng-metadata utilities not working 😄

michaelkrog commented 8 years ago

This may be a dumb question, but it is still not clear to me how I can create a test that injects one of my own typescript classes?

Take the example from https://hotell.gitbooks.io/ng-metadata/content/docs/recipes/service.html in which the UserComponent uses the @Injectable UserService.

// user.service.ts
import { Inject, Injectable } from 'ng-metadata/core';

@Injectable()
export class UserService{

  hobbies: string[] = [];

  constructor( @Inject('$http') private $http: ng.IHttpService ){}

  addHobby(name: string){
     this.hobbies.push(name);
  }

  getInfo(){
    return this.$http.get('/foo/bar/info')
      .then((response)=>response.data);
  }

}
// user.component.ts
import { Component } from 'ng-metadata/core';

import { UserService } from './user.service';

@Component({
  selector:'my-user',
  providers: [ UserService ],
  template: `...`
})
export class UserComponent{}

Is it possible to test this scenario? It is not clear to me from https://hotell.gitbooks.io/ng-metadata/content/docs/api/testing. It shows the possibility of injecting a plain old angular service, but how should UserServicebe injected?

aciccarello commented 8 years ago

@michaelkrog, currently the easiest way to do this is to use the getInjectableName() function to get the dependency injection string and the angular $injector service to get the instance of your service.

In the future you will be able to use angular 2-like apis such as

import { inject } from 'ng-metadata/testing';
import { UserService } from './user.service';

inject([UserService], (userService: UserService) => {
  // Use instance of UserService in test
});
michaelkrog commented 8 years ago

Ahh ok.. Got it now. I can inject a mocked service like this:

let authenticationServiceString = getInjectableName(AuthenticationService);

  beforeEach( angular.mock.module( TestModule, ($provide) => {
      $provide.value(authenticationServiceString, new MockAuthenticationService());
  }));

That will make my mock available as AuthenticationService in the component I am testing.

aciccarello commented 8 years ago

Yep, angular.mock.module also accepts an object mapping mocks. Same effect but different option for syntax.

let authenticationServiceString = getInjectableName(AuthenticationService);

  beforeEach( angular.mock.module( TestModule, {
      [authenticationServiceString]: new MockAuthenticationService()
  }));