clicker icon indicating copy to clipboard operation
clicker copied to clipboard

General Testing Help / Advice

Open lathonez opened this issue 8 years ago • 40 comments

The purpose of this issue is a sticky thread general for help with testing Ionic 2 projects.

E.g. you have our testing framework set up fine but you don't know how to test a particular scenario / component / feature, or you're getting unexpected results from some of your tests.

The correct place for posting such questions is stack overflow. Asking your question on stack will:

  • give you a wider audience
  • give you a better quality answer
  • give you a quicker answer

Please follow these guidelines:

  1. post your question on stack
  2. tag your question with angular2 and angular2-testing. You can tag ionic2 too if your question is about testing some Ionic component
  3. comment on this issue with a link to your question. This will draw our attention to it and we'll answer on stack if we can help. It will also provide a good reference point for others.

Further issues raised on this repo will be referred here.

If you're having trouble setting up testing (e.g. you can't get a single basic test running against your app) please raise an issue here - don't use stack for that

lathonez avatar Dec 09 '16 12:12 lathonez

http://stackoverflow.com/questions/41164040/protractor-click-in-ionic-collection-view

valburyakov avatar Dec 15 '16 12:12 valburyakov

http://stackoverflow.com/questions/41970496/ionic-2-navcontroller-unit-testing

emroussel avatar Feb 01 '17 01:02 emroussel

Anyone try emitting viewCtrl.willEnter during their specs? ie, expect someFunction().toHaveBeenCalled after mimicking that a Component is being entered?

https://forum.ionicframework.com/t/how-to-mock-viewcontroller-willenter-in-tests/78205

kamok avatar Feb 02 '17 16:02 kamok

How to test native functionality on a device or simulator?

Baadier-Sydow avatar Mar 06 '17 14:03 Baadier-Sydow

@Baadier-Sydow #52

lathonez avatar Mar 06 '17 20:03 lathonez

@lathonez I want to ask you about having a single test.ts. It seems like a more sane approach in managing all the dependencies than putting it in every single spec file. But, is there a drawback in importing every single dependency on every single spec file? I feel like it would slow down the time it takes for tests to run. Have you tested it? If not, I have an app where we would be considering consolidating everything into a single test.ts and I could report with results when that's done. But then again we're not using angular-cli on that.

kamok avatar Apr 18 '17 19:04 kamok

@kamok I've never heard this suggested before. The argument is typically between whether to have *.spec* in-line with source code or in a separate spec` folder.

See https://angular.io/styleguide#!#single-responsibility

I do follow what you mean about dependencies, but I have found it OK (testing no worse than typical angular2) using the utilities in test.ts as opposed to the boilerplate required without them: https://github.com/lathonez/clicker/blob/master/src/pages/page2/page2.spec.ts#L13-L33

lathonez avatar Apr 18 '17 21:04 lathonez

When running the test cases, getting the error "Uncaught TypeError: Cannot read property '_getPortal' of undefined throw"

https://stackoverflow.com/questions/44149489/ionic-2-unit-testing-uncaught-typeerror-cannot-read-property-getportal-of

niharika30 avatar May 24 '17 04:05 niharika30

You are probably missing something from the mocks - testing some Ionic component that we have not tested before.

Search the Ionic source code for _getPortal.

On Wed, 24 May 2017 at 12:34, Niharika Shrivastava [email protected] wrote:

When running the test cases, getting the error "Uncaught TypeError: Cannot read property '_getPortal' of undefined throw"

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/lathonez/clicker/issues/191#issuecomment-303615162, or mute the thread https://github.com/notifications/unsubscribe-auth/AG5tSJhbGp7LOgCD9hkpnzzUvgZAzRThks5r87M8gaJpZM4LI56M .

lathonez avatar May 28 '17 12:05 lathonez

How do you guys manage large test bases? Right now, it takes about 10 seconds to run 15 tests for me, and most are just component init tests. As it gets larger, how do you guys manage it? How to keep the process of writing test => confirming tests works => writing test... fast?

It's obvious that my set up has some issues, which will need to be figured out. Can anyone with a greater amount of components and number of test cases reply with their speed?

kamok avatar Jun 02 '17 15:06 kamok

We have about 600 tests in a closed source project. The tests take about 2 and a half minutes to run.

When developing I usually run a single suite with fdescribe, and just let the full tests run on CI.

lathonez avatar Jun 03 '17 01:06 lathonez

I use wallaby any interactive test runner.

A slight tweak is required to use with the clicker repo - see https://github.com/wallabyjs/public/issues/620

murraybauer avatar Jun 05 '17 09:06 murraybauer

@lathonez, how big is your project? I managed to improve my speed to 1 spec per second, but yours is about 4 per second. What can influence such a massive difference in speed?

@murraybauer I'll look into it if my attempts at optimizations fail. Thanks you.

kamok avatar Jun 05 '17 14:06 kamok

Number of tests doesn't really affect run time after a certain point. We have around 1700 tests and they run in around 3 mins or so. Keep in mind that async tests greatly impact that time (we have a couple dozen integration tests taking a few hundred mills each), so we have used fakeasync where possible. Nowadays we are all using wallaby.js which makes testing much more responsive and we don't even thing about how long karma takes for local development (runs before push and on CI only).

masimplo avatar Jun 05 '17 14:06 masimplo

I ran into this Issues where using the new Keyboard would cause a no provider error. You can replicate it by using the new ionic native keyboard import. https://github.com/ionic-team/ionic-plugin-keyboard/issues/281

Any ideas on why this happens?

kamok avatar Jun 08 '17 15:06 kamok

We have been using the new keyboard without issues for a while.

On Fri, 9 Jun 2017 at 01:31, Ka Mok [email protected] wrote:

I ran into this Issues where using the new Keyboard would cause a no provider error. You can replicate it by using the new ionic native keyboard import. ionic-team/ionic-plugin-keyboard#281 https://github.com/ionic-team/ionic-plugin-keyboard/issues/281

Any ideas on why this happens?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/lathonez/clicker/issues/191#issuecomment-307139786, or mute the thread https://github.com/notifications/unsubscribe-auth/AG5tSPqdV8xUk2_60r_csT1tGHvVzjr3ks5sCBO3gaJpZM4LI56M .

lathonez avatar Jun 08 '17 21:06 lathonez

@lathonez I figured out the new ionic Keyboard from @ionic-native/keyboard. We ended up not using TestUtils when we had Keyboard, and doing the test module configuration inside the spec itself. Is that how you guys did it?

kamok avatar Jun 16 '17 15:06 kamok

@kamok @lathonez I've been grappling with the problem I think you're describing, when to use TestUtils and when you need a more tailored testmodule. I have a project where I'm using the TestUtil to provide the base "ambient" providers but then I have an additional parameter that allows me to pass in additional providers that I may like a reference to or as additional dependencies. Have you guys come up with a better way?

TestUtils

public static beforeEachCompiler(components: any[], providers: any[]): Promise<{ fixture: any, instance: any }> {
		return TestUtils.configureIonicTestingModule(components, providers)
			.compileComponents().then(() => {
				let fixture: any = TestBed.createComponent(components[0]);
				return {
					fixture: fixture,
					instance: fixture.debugElement.componentInstance,
				};
			});
	}

	public static configureIonicTestingModule(components: Array<any>, componentProviders: any[]): typeof TestBed {
		let coreProviders: any[] = [
			App,
			DomController,
			GestureController,
			{provide: Keyboard, useFactory: () => KeyboardMock.instance()},
			{provide: MenuController, useFactory: () => MenuControllerMock.instance()},
			{provide: Form, useFactory: () => FormMock.instance()},
			{provide: Config, useFactory: () => ConfigMock.instance()},
			{provide: TranslateService, useFactory: (TranslateServiceMock.instance)},
			{provide: Platform, useFactory: () => PlatformMock.instance()},
		];

		let providers: any[] = coreProviders.concat(componentProviders);

		return TestBed.configureTestingModule({
				imports: [IonicModule, CommonModule],
				declarations: [components, TranslatePipeMock ],
				providers: providers
			});
	}

which in use looks something like:

beforeEach(async(() => {

			form = FormGroupMock.instance(true, formVal);
			viewCtrl = ViewControllerMock.instance();
			formBuilder = FormBuilderMock.instance(form);
			settingsSrvc = SettingsServiceMock.instance();
			dateSrvc = DateServiceMock.instance();


			let providers: any[] = [
				{provide: FormBuilder, useFactory: () => formBuilder},
				{provide: SettingsService, useFactory: () => settingsSrvc},
				{provide: ViewController, useFactory: () => viewCtrl},
				{provide: DateService, useFactory: () => dateSrvc}
			];

			return TestUtils.beforeEachCompiler([ReportCreatePage], providers)
				.then(compiled => {

					fixture = compiled.fixture;
					instance = compiled.instance;
					classUnderTest = fixture.componentInstance;

					fixture.detectChanges();
				});
		}
	));

stonelasley avatar Jul 07 '17 17:07 stonelasley

The way I do this is defining different "sets" of providers for different usages, and assigning each to a static variable against TestUtils.

Instead of declaring the providers in the beforeEach and passing them in, I pass through this variable TestUtils.ReportProviders which would then add the providers as necessary.

It's basically the same as what you have there, I just have lots of spec and it saves me from importing the additional providers each time.

In practice I think I have four different sets that I use.

lathonez avatar Jul 11 '17 21:07 lathonez

https://stackoverflow.com/questions/45311260/how-to-test-an-asynchronous-function-using-karma-jasmine-in-ionic2

jainAdijain avatar Jul 26 '17 09:07 jainAdijain

Hi. I have trouble testing a component containg a FAB. Maybe you can give me a hint how to solve this. https://stackoverflow.com/questions/45711222/how-to-test-a-floating-action-button-in-ionic2

euleule avatar Aug 16 '17 10:08 euleule

The error is coming from UIEventManager: https://github.com/ionic-team/ionic/blob/master/src/gestures/ui-event-manager.ts#L48

this.evts is an array of 1 undefined element, hence the error.

this.evts is set in UIEventManager.listen, which uses Platform: https://github.com/ionic-team/ionic/blob/master/src/gestures/ui-event-manager.ts#L40

So we need to look at our platform mock: https://github.com/stonelasley/ionic-mocks/blob/master/src/angular/platform.ts#L26

Adding this line solves for me. Please raise a PR against ionic-mocks to fix!

instance.registerListener.and.returnValue(() => {});

Thanks

lathonez avatar Aug 16 '17 14:08 lathonez

how is everyone testing classes with asynchronous tasks in their constructor? I've started using the following approach but I'd love to find a better way.

class MyClass { 
 constructor(platform: Platform, service: MyService) {


    this.platform = platform;
    this.translateService = translateService;

    this.initialize();
  }

  private initialize(service: MyService): Promise<any> {
	return service.doSomeAsync()
  }
 }
  
  
 /* Spec */
 
 describe('MyClass', () => {
 
    let serviceMock: any;
	let classUnderTest: MyClass;
	beforeEach(() => {
		classUnderTest = new MyClass(serviceMock);
	);
	
	describe('initialize', done => {
               //initialize can also be protected and then I'll extend the class under test in the spec but this is the lazy approach. 
		classUnderTest['initialize']()
		.then(() => {
			//TEST ASYNC CONSTRUCTOR LOGIC
			done();
		});
	
	});
 }

stonelasley avatar Oct 08 '17 19:10 stonelasley

@stonelasley - I do the same as you. Did some research a while back and it seemed the best way. Also keen to improve if possible.

lathonez avatar Oct 08 '17 20:10 lathonez

@stonelasley Another way would be to use the lifecycle events of angular and ionic to decouple object creation and object initialization. In that case you could test the initialization method as you would test any method containing async calls.

euleule avatar Oct 10 '17 14:10 euleule

@euleule that's a good point and that's also an approach I've used. It points out a weakness in my example. I think I should have provided the initialize to the platform.ready callback. Nonetheless, I like your phrase "decouple object creation and initialization".

stonelasley avatar Oct 10 '17 21:10 stonelasley