How do I mock polymer core ajax, for unit testing

那年仲夏 提交于 2019-12-08 21:23:16

问题


I am building the scaffolding for my new polymer project, and am considering unit tests. I think I will be using the karma/jasmine combination. There is an interesting post at http://japhr.blogspot.co.uk/2014/03/polymer-page-objects-and-jasmine-20.html which I understand enough to get me started, but the key question I will have to address and haven't found any standard way to do it is how do I mock the ajax calls.

When I was using jasmine, standalone, on a JQuery Mobile project, I was able to directly use the Jasmine SpyOn ability to mock the JQuery.ajax call. Is there something similar for Polymer?

I came across an element <polymer-mock-data> but there is no real documentation for it, so I couldn't figure out if they might help


回答1:


Instead of importing core-ajax/core-ajax.html, create your own core-ajax element.

<polymer-element name="core-ajax" attributes="response">
<script>
  Polymer('core-ajax', {
    attached: function() {
      this.response = ['a', 'b', 'c'];
    }
  });
</script>
</polymer-element>

Obviously, this is just an example, the actual implementation depends on the desired mocking behavior.

This is just one way to solve it, there are many others. I'm interested to hear what you find (in)convenient.




回答2:


It turns out that Jasmine2.0 has an Jasmine-ajax plugin that will mock the global XMLHttpRequest. core-ajax uses this under the hood, so I can directly get at the call.

It works well, in a beforeEach function at the top the suite you call jasmine.Ajax.install and in the afterEach function you call jasmine.Ajax.uninstall, and it automatically replaces the XMLHttpRequest.

Timing is also crucial, in that you need to ensure you have mocked the Ajax call before the element under test uses it. I achieve that using a separate function to specifically load the fixture which contains the element under test, which is called after jasmine.Ajax.install has been called. I use a special setup script thus

(function(){
  var PolymerTests = {};
  //I am not sure if we can just do this once, or for every test.  I am hoping just once
  var script = document.createElement("script");
  script.src = "/base/components/platform/platform.js";
  document.getElementsByTagName("head")[0].appendChild(script);

  var POLYMER_READY = false;
  var container;  //Used to hold fixture
  PolymerTests.loadFixture = function(fixture,done) {
    window.addEventListener('polymer-ready', function(){
      POLYMER_READY = true;
     done();
    });
    container = document.createElement("div");
    container.innerHTML = window.__html__[fixture];
    document.body.appendChild(container);
    if (POLYMER_READY) done();
  };
  //After every test, we remove the fixture
  afterEach(function(){
    document.body.removeChild(container);
  });

  window.PolymerTests = PolymerTests;

})();

The only point to note here is that the fixture files have been loaded by the karma html2js pre-processor, which loads them into the window.__html__ array, from where we use the code to add to the test context

My test suite is like so

describe('<smf-auth>',function(){
  beforeEach(function(done){
    jasmine.Ajax.install();
    PolymerTests.loadFixture('client/smf-auth/smf-auth-fixture.html',done);
  });
  afterEach(function(){
    jasmine.Ajax.uninstall();
  });
  describe("The element authenticates",function(){
    it("Should Make an Ajax Request to the url given in the login Attribute",function(){
      var req = jasmine.Ajax.requests;
      expect(req.mostRecent().url).toBe('/football/auth_json.php'); //Url declared in our fixture
    });

  })
});



回答3:


For this answer, I took an entirely different approach. Inspiration came from Web Component Tester, which includes sinon within its capabilities. sinon includes the ability to call sinon.useFakeXMLHttpRequest to replace the standard xhr object that core-ajax uses and return responses baked on that.

As far as I can see, haven't quite got as far as running module tests using it, Web Component Tester runs sinon in the node.js context so the build of sinon supplied with it can "require" the various sinon components. In a normal browser environment this doesn't work and I was looking for a way to allow me to manually run the app I was developing without a php capable server running..

However, downloading and installing with Bower the actual releases from the sinonjs.org web site, does provide a completely built sinon that will run in the context of a web server.

So I can include the following scripts in my main index.html file

  <!--build:remove -->
  <script type="text/javascript" src="/bower_components/sinon-1.14.1/index.js"></script>
  <script type="text/javascript" src="/fake/fake.js"></script>
  <!--endbuild-->

which is automatically removed by the gulp build scrips and then fake JS has the following in it

var PAS = (function (my) {
  'use strict';
  my.Faker = my.Faker || {};
  var getLocation = function(href) {
    var a = document.createElement('a');
    a.href = href;
    return a;
  };
  sinon.FakeXMLHttpRequest.useFilters = true;
  sinon.FakeXMLHttpRequest.addFilter(function(method,url){
      if(method === 'POST' && getLocation(url).pathname.substring(0,7) === '/serve/') {
        return false;
      }
      return true;
  });
  var server = sinon.fakeServer.create();
  server.autoRespond = true;


  my.Faker.addRoute = function(route,params,notfound){
    server.respondWith('POST','/serve/' + route + '.php',function(request){
      var postParams = JSON.parse(request.requestBody);
      var foundMatch = false;
      var allMatch;
      /*
       * First off, we will work our way through the parameter list seeing if we got a parameter
       * which matches the parameters received from our post.  If all components of a parameter match,
       * then we found one
       */

      for(var i=0; i <params.length; i++) {
        //check to see parameter is in request
        var p = params[i][0];
        allMatch = true;  //start of optimisic
        for(var cp in p ) {
          //see if this parameter was in the request body
          if(typeof postParams[cp] === 'undefined') {
            allMatch = false;
            break;
          }


          if(p[cp] !== postParams[cp]) {
            allMatch = false;
            break;
          }
        }
        if (allMatch) {
          request.respond(200,{'Content-Type':'application/json'},JSON.stringify(params[i][1]));
          foundMatch = true;
          break;
        }

      }
      //see if we found a match.  If not, then we will have to respond with the not found option
      if (!foundMatch) {
        request.respond(200,{'Content-Type':'application/json'},JSON.stringify(notfound));
      }
    });

  };
  return my;
})(PAS||{});
/**********************************************************************
  Thses are all the routinee we have and their responses.
 **********************************************************************/
PAS.Faker.addRoute('logon',[
    [{password:'password1',username:'alan'},{isLoggedOn:true,userID:1,name:'Alan',token:'',keys:['A','M']}],
    [{username:'alan'},{isLoggedIn:false,userID:1,name:'Alan'}],
    [{password:'password2',username:'babs'},{isLoggedOn:true,userID:2,name:'Barbara',token:'',keys:['M']}],
    [{username:'babs'},{isLoggedIn:false,userID:2,name:'Barbara'}]
    ],{isLoggedOn:false,userID:0,name:''});

The PAS function initialises a sinon fake server and provides a way of providing tests cases with the addRoute function. For a given route, it checks the list of possible POST parameter combinations, and as soon as it finds one, issues that response.

In this case testing /serve/logon.php for various combinations of username and password. It only checks the parameters actually in the particular entry.

So if username = "alan" and password = "password1" the first response is made, but if username is "alan" and any other password is supplied - since it isn't checked, the second pattern matches and the response to that pattern is made.

If non of the patterns match, the last "notfound" parameter is the response pattern that is made.

I believe I could use this same technique in my module test fixtures if I wanted to, but I am more likely to do more specific sinon spying and checking actual parameters in that mode




回答4:


For 0.8, the tests for PolylmerElements/iron-ajax show how to do this with sinon.

Since SO doesn't like link-only answers, I've copied their code below. However I'd highly recommend going to the source linked above, since 0.8 components are in a high state of flux currently.

      var jsonResponseHeaders = {
        'Content-Type': 'application/json'
      };
      var ajax;
      var request;
      var server;

      setup(function () {
        server = sinon.fakeServer.create();
        server.respondWith(
          'GET',
          '/responds_to_get_with_json',
          [
            200,
            jsonResponseHeaders,
            '{"success":true}'
          ]
        );

        server.respondWith(
          'POST',
          '/responds_to_post_with_json',
          [
            200,
            jsonResponseHeaders,
            '{"post_success":true}'
          ]
        );

        ajax = fixture('TrivialGet');
      });

      teardown(function () {
        server.restore();
      });

      suite('when making simple GET requests for JSON', function () {
        test('has sane defaults that love you', function () {
          request = ajax.generateRequest();

          server.respond();

          expect(request.response).to.be.ok;
          expect(request.response).to.be.an('object');
          expect(request.response.success).to.be.equal(true);
        });

        test('will be asynchronous by default', function () {
          expect(ajax.toRequestOptions().async).to.be.eql(true);
        });
      });


来源:https://stackoverflow.com/questions/24531473/how-do-i-mock-polymer-core-ajax-for-unit-testing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!