2

I am having the hardest time mocking the server response to an iron-ajax component inside my custom component. Here is my code files.

custom-component.html:

<link rel="import" href="/iron-ajax/iron-ajax.html">
<link rel="import" href="/internal-component/internal-component.html">

<dom-module id="custom-component">
    <template>
        <iron-ajax url="staticFile.json" auto handle-as="json" last-response={{ajaxResponse}}></iron-ajax>
        <template is="dom-repeat"
                  items={{ajaxResponse}}
                  sort="_sort"
                  id="gridRow">
            <internal-component var1={{item.var1}}
                                   var2={{item.var2}}>
            </internal-component>
        </template>
    </template>
</dom-module>
<script>(some cool scripts that are working...)</script>

custom-component-tests.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <script src="/webcomponentsjs/webcomponents-lite.js"></script>
    <script src="/web-component-tester/browser.js"></script>
    <script src="/test-fixture/test-fixture-mocha.js"></script>

    <link rel="import" href="/test-fixture/test-fixture.html" />

    <link rel="import" href="/polymer/polymer.html">
    <link rel="import" href="/polymer-ts/polymer-ts.html">

    <link rel="import" href="custom-component.html">
</head>
<body>
<test-fixture id="testElement">
    <template>
        <custom-component></custom-component.>
    </template>
</test-fixture>

<script>
    suite('<custom-component>', function () {
        var testElement;
        var server;
        var responseHeaders = {
            json: { 'Content-Type': 'application/json' },
            plain: { 'Content-Type': 'text/plain' }
        };
        setup(function () {
            replace('custom-component').with('fake-custom-component');
            server = sinon.fakeServer.create();
            server.respondWith('GET', /staticFile\.json/, [
                200,
                responseHeaders.json,
                '[{"var1": "9a","var2": "17n"}]'
            ]);
            testElement = fixture("testElement");
        });
        teardown(function () {
            server.restore();
        });
        suite('testSuite', function () {
            test('test1', function () {
                var ajax = testElement.getElementsByTagName('iron-ajax')[0];
                ajax.lastResponse = null;
                ajax.generateRequest();
                server.respond();
                assert(ajax.lastResponse.hour === "9a");
            });
        });
    });
</script>

</body>
</html>

You'll notice that I'm explicitly calling the iron-ajax generateRequest because if I didn't, then the request wouldn't even happen until after my test completed (and failed). When calling generateRequest explicitly, I am at least able to make the request happen, but (even though I am calling server.respond()) iron-ajax doesn't call _handleResponse until after the test completes. And, even when it does, it is not setting lastResponse because there is a line of code in iron-ajax that checks if (request === this.lastRequest) (which it isn't).

What am I doing wrong?

progressquest
  • 43
  • 1
  • 6

3 Answers3

4

I have found a better solution to test the auto functionality of iron-ajax elements in my elements.

You need to add an event listener on the request of your ajax to fire your server response, no need for generateRequest and no need for setTimeout hacks.

here is an example:

            test('test with ajax element', function (done) {
                var ajax = Polymer.dom(myElement.root).querySelector("#ajax_element");

                ajax.addEventListener('request', function (e) {
                    server.respond('GET', '/CALLED_URL', dataResponse);
                });

                ajax.addEventListener('response', function (e) {
                    //DO YOUR EXPECTS HERE
                    done();
                });
            });
inkubux
  • 236
  • 2
  • 11
  • 2
    I did my mocking slightly differently: by overriding generateRequest. Since I want to test my element and not the entire business of sending asynch requests etc, I do the following: `stub('iron-ajax', { generateRequest() { this._setLastResponse({/*my response object */}); } });` If you want to make use of the 'on-response' event you then do something like this in the fake generateRequest method: `this.fire('response', {/*my response object */});` – Paysdoc Aug 28 '16 at 12:25
2

I guess a good night's sleep is what I needed.

I forgot to account for asynchronous requests. I have amended the code to now reflect the following:

suite('<custom-component>', function () {
    var testElement;
    var server;
    var responseHeaders = {
        json: { 'Content-Type': 'application/json' },
        plain: { 'Content-Type': 'text/plain' }
    };
    setup(function () {
        replace('custom-component').with('fake-custom-component');
        server = sinon.fakeServer.create();
        server.respondWith('GET', /staticFile\.json/, [
            200,
            responseHeaders.json,
            '[{"var1": "9a","var2": "17n"}]'
        ]);
        testElement = fixture("testElement");
    });
    teardown(function () {
        server.restore();
    });
    suite('testSuite', function () {
        // note that I added the "done" as a parameter on my test function
        test('test1', function (done) {
            var ajax = testElement.getElementsByTagName('iron-ajax')[0];
            ajax.generateRequest();
            server.respond();

            // note that I added an async event listener here, and moved my test inside.
            ajax.addEventListener('response', function(e) {
                assert(e.target.lastResponse.var1 === "9a");
                done();
            }
        });
    });
});

I am now able to properly intercept the response, and the test is mostly working as expected. I still have the problem that the iron-ajax's lastResponse is not set, but that is a different question I think.

progressquest
  • 43
  • 1
  • 6
  • For those wondering why my lastRequest wasn't being set: I have auto set to true, which creates a request as soon as the component is created. However, I never give up control in javascript, so the initial request doesn't happen. Manually calling generateRequest caused a second request, which did not match the first request, so it didn't set the lastResponse. The solution was to wrap my test function inside a setTimeout with 0 timeout, and abandon the manual call to generateRequest. This allowed the initial iron-request to be generated. – progressquest Dec 11 '15 at 16:28
  • I have an iron-ajax inside a polymer component, this solution is not working for me. – Ishank May 18 '17 at 08:15
  • Adding extra listener for test and calling done function inside of it! That's the answer. Thanks. – burak Aug 14 '17 at 14:30
0

As a component test I believe you should really be testing the component behaviour rather than sinon and/or the iron-ajax element internals. Your request will most probably update some property which will trigger some DOM updates.

I've also always hated setTimeouts in tests, and just found out WCT has a flush method for those cases (it awaits for all async stuff to finish) - it's just not documented on the AJAX testing section, but on the DOM manipulation instead (https://www.polymer-project.org/1.0/docs/tools/tests#test-local-dom).

As such, my approach is:

suite('testSuite', function () {
  test('test1', function (done) {
    // assuming iron-ajax with auto
    server.respond();
    flush(function () {
      expect(el.myObject).to.deep.equal(responseObj);

      // in case you're binding the last response to a property
      expect(el.lastResponse).to.deep.equal(responseObj);

      done();
    })
  });
});