How to over ride a function of component in integration test in ember Qunit testing

1.3k Views Asked by At

I'm writing my first question here sorry for any ambiguity.

I write an integration test for update-pw component which simple render update-pw and then fill input field with fillIn and then click save button which trigger the action savePW in update-pw.js. I only pass email(for whom we want to change password) and new password.

savePW() function further has a function call self.store.updateSingleUserPw(email, newPw) which is written in service store.js.

updateSingleUserPw(email, newPw) returns a promise after server process on API call. On basis of fulfillment or rejection of promise I show a modal. I just want to make that promise fulfill or rejected in my test instead of server response for promise.

// integration/component/update-pw-test.js

import { module, test } from 'qunit';
import EmberObject from '@ember/object';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import Service from '@ember/service';

module('Integration | Component | update-pw', function(hooks) {
  setupRenderingTest(hooks);

  const store = Service.extend({
    savePW() {
      self.store.updateSingleUserPw(email, newPw, function() {
          console.log('this is function overriding', email, newPw);
          return true;
        })
        .then(function() {
          // Reset controller fields
          self.set('password', '');
          self.set('updateModal', false);
          swal({
            title: 'Das hat geklappt',
            type: 'success'
          });
        }, function() {
          self.set('updateModal', false);
          swal({
            title: 'problems with setting new pw.',
            type: 'error'
          });
        })
        .finally(function() {
          self.set('changingPassword', false);
        });
    }
  });

  test('it renders', async function(assert) {
    this.application.register('service:store', store);
    this.application.inject.service('store', { as: 'store' });
    assert.expect(2);
    this.set('updateModal', true);
    this.set('testing', true);
    let currentUpdateAdmin = EmberObject.create({
      username: 'steinauer',
      email: '[email protected]'
    });
    this.set('currentUpdateAdmin', currentUpdateAdmin);
    await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);

    assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
    await fillIn('#password', 'test123456');
    await click('.save-button');
    // Template block usage:
    await render(hbs`
      {{#update-pw}}
        template block text
      {{/update-pw}}
    `);

    // assert.equal(this.element.textContent.trim(), 'what is this');
  });
});    
// components/update-pw.js

import Component from '@ember/component';

export default Component.extend({
  changingPassword: false,

  actions: {
    savePW() {
      let self = this;
      if (!self.get('currentUpdateAdmin.email'))
        return;

      let newPw = self.get('password');
      let email = self.get('currentUpdateAdmin.email');
      self.set('changingPassword', true);

      if (!email)
        return;

      self.store.updateSingleUserPw(email, newPw)
        .then(function() {
          // Reset controller fields
          self.set('password', '');
          self.set('updateModal', false);
          swal({
            title: 'Das hat geklappt',
            type: 'success'
          });
        }, function() {
          self.set('updateModal', false);
          swal({
            title: 'problems with setting new pw',
            type: 'error'
          });
        })
        .finally(function() {
          self.set('changingPassword', false);
        });

    }
  }
});

function in Service/store.js :

updateSingleUserPw(email, newPw) {
  let headers = this.get('headers');

  return new Promise(function(resolve, reject) {
    $.ajax({
      type: 'POST',
      url: ENV.api + '/accounts/updateSingleUserPw',
      data: {
        email: email,
        pwNew: newPw
      },
      headers,
      dataType: 'json'
    }).then(function(success) {
      if (success) {
        resolve(newPw);
      } else {
        reject('password change failed');
      }
    }, function(xhr, status, error) {
      reject(error);
    });
  });
}

Before trying to override function I got only rejected promise modal but after the try of overriding the function i'm getting: Promise rejected during "it renders": Cannot read property register of undefined.

1

There are 1 best solutions below

2
real_ate On

thanks for your question

Firstly can I thank you for providing your code samples, I would not have been able to solve your question had you not provided so much! I have actually simplified some of the things that you are trying to do and I think by simplifying things I have come to the solution.

Firstly I have renamed the Service that you keep using to be called password-store. Usually when an Ember developer sees a Service named store they tend to think of an ember-data store which I'm assuming you're not actually using here by the functionality that you are expecting.

I generated a very simple mock store that just had one function in it:

// app/services/password-store.js
import Service from '@ember/service';

export default Service.extend({
  updateSingleUserPw(email, password) {
    // TODO: do something with email & password
    return Promise.resolve();
  }
});

This just returns a promise so that it won't break any of the other code samples. I then updated your update-pw component to use the new password store:

// app/components/update-pw.js

import Component from '@ember/component';
import { inject as service } from '@ember/service';

function swal() {
  // noop - not sure where this comes from
}

export default Component.extend({
  passwordStore: service(),
  changingPassword: false,

  actions: {
    savePW() {
      if (!this.get('currentUpdateAdmin.email'))
        return;

      let newPw = this.get('password');
      let email = this.get('currentUpdateAdmin.email');
      this.set('changingPassword', true);

      if (!email)
        return;

      this.passwordStore.updateSingleUserPw(email, newPw)
        .then(() => {
          // Reset controller fields
          this.set('password', '');
          this.set('updateModal', false);
          swal({
            title: 'Das hat geklappt',
            type: 'success'
          });
        }, () => {
          this.set('updateModal', false);
          swal({
            title: 'problems with setting new pw',
            type: 'error'
          });
        })
        .finally(() => {
          this.set('changingPassword', false);
        });
    }
  }
});

I also added a swal() function because I didn't quite know where that came from in your example. It seemed to be missing so I just ignored it.

Now lastly I have setup a template so that the test will actually pass:

// app/templates/components/update-pw.hbs

<h4>set new PW for steinauer</h4>

{{input id="password" value=password}}

<button type="button" name="button" class="save-button" {{action 'savePW'}}></button>

Now with the application fully setup here is the full example of a test that will do exactly what you were hoping to do:

// tests/integration/components/update-pw-test.js

import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import StoreService from 'your-app-name/services/password-store';

module('Integration | Component | update-pw', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function(assert) {
    const passwordStore = StoreService.extend({
      updateSingleUserPw(email, newPw) {
        console.log('updateSingleUserPw override!!');

        assert.equal(newPw, 'test123456');
        return Promise.resolve();
      }
    });

    this.owner.register('service:password-store', passwordStore);

    assert.expect(2);
    this.set('updateModal', true);
    this.set('testing', true);
    let currentUpdateAdmin = {
      username: 'steinauer',
      email: '[email protected]'
    };
    this.set('currentUpdateAdmin', currentUpdateAdmin);
    await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);

    assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
    await fillIn('#password', 'test123456');
    await click('.save-button');
    // Template block usage:
    await render(hbs`
      {{#update-pw}}
        template block text
      {{/update-pw}}
    `);
  });
});

The first thing that you might notice is that we are not using this.application.register or this.application.inject. I can't remember exactly if this is how it used to be done a long time ago but this is not available for a few years in Ember.

What we end up doing is we import the StoreService from your-app-name/services/password-store (replacing your-app-name with whatever your modulePrefix is) and then we extend it while overriding the updateSingleUserPw() function. In your example it looked like you were trying to override a function called savePW() but that is actually the action name from the component and it might have been slightly confusing you.

I hope that helps, I have tested the example locally and it works perfectly well! You may also notice I added an assertion inside the service, this is quite a useful pattern to make sure that the service receives the right arguments from the component