2

I have just started learning angular unit testing. However, this test on a function with http call fails. I have pin pointed the problem but however I am not being able to fix it. I know it's some simple issue

Controller

//Get data from URL
vm.getJson = function() {
    var url = 'https://www.reddit.com/r/worldnews/new.json',
        count = 0;
    $http.get(url).success(function(response) {
        console.log(response);
        for (var i = 0; i < response.data.children.length; i++) {
            vm.data.push(response.data.children[i].data);
            count++;
            if (count === response.data.children.length) {
                vm.numberOfPages();
            }
        }

        vm.result = true;

    }).error(function(err) {
        console.log(err);
    });

};

The response I am getting is: enter image description here

Spec

 //Testing the getJson function
describe('vm.getJson()', function() {

   it('It should return dummy Data as response and vm.result to be truthy', function() {

    var dummyData = {name: 'Umair'};
    $httpBackend.whenRoute('GET','https://www.reddit.com/r/worldnews/new.json').respond(200, dummyData);

    MainCtrl.getJson(); 

    $httpBackend.flush();

    expect(MainCtrl.result).toBeTruthy();


}); });

I don't get any errors and the test passes if I remove the loop from the controller function. The error I am getting is:

Cannot read 'Children' of undefined. From the image I have attached with the response data, children is the array.

Umair Sarfraz
  • 177
  • 3
  • 5
  • 16

4 Answers4

3

When your test is run, $httpBackend actually intercepts the $http.get call and assign dummyData to the response as you indicated in

$httpBackend.whenRoute('GET','https://www.reddit.com/r/worldnews/new.json').respond(200, dummyData);

This mocking behavior allows your unit tests to be completed quickly without being reliant on reddit being reachable from your test machine. So in your controller, response.data = {name: 'Umair'} and that object has no child named children.

To fix this, for dummyData, try mimicking the real data a bit more.

lastoneisbearfood
  • 3,955
  • 5
  • 26
  • 25
  • Thank you. I tried mimicking the real data as per the other answer here and it worked. Thank you. I am still confused however on what you meant by 'This mocking behaviour allows your unit test to be completed without being reliant'? How does that work? – Umair Sarfraz Jun 25 '16 at 09:19
  • 1
    Before you got to this testing phase, you had to setup the testing framework and included something called `angular-mock`. $httpBackend comes with it, and the idea is to intercept $http calls and use mock data (like your dummyData). The intent of unit-testing is to test a small piece of code, like 1 single function by passing in some sample input and expecting a specific output/behavior. Mocking allows you to do just that. If your test fails, you know an error has been introduced to your function, and it has nothing to do with reddit or your network connection. – lastoneisbearfood Jun 25 '16 at 09:27
  • Yeah all right. Going to accept it as the answer. Just one last question though, the mock I create, it has to be matched with the original data? Like the original response had a property children and in the mock it is compulsory to add a same property key? – Umair Sarfraz Jun 25 '16 at 09:32
  • The additional benefit is that while $http.get is truly asynchronous, and using it may cause your test run time to vary. $httpBackend and it's `flush` function allow you to test an asynchronous mechanism in a synchronous way. The response does not arrive to your controller until `flush` is called. So you could have done a bunch of other stuff before calling `flush` to simulate some real work asynchronous situations or side effects. – lastoneisbearfood Jun 25 '16 at 09:33
  • The mock doesn't have all the entire original data. It doesn't even need the entire structure. It just needs whatever the heck your controller function actually deals with. That's the subject of your unit test. The intent is to alert you to future changes to your controller function that cause the test to fail. You may then realize your testing data is not good enough, or there is a real error in the controller function. – lastoneisbearfood Jun 25 '16 at 09:34
  • 1
    Ah I see. I understand. Thank you so very much. – Umair Sarfraz Jun 25 '16 at 09:35
1

You're returning an object with a property name in your test and you're then trying to access the property data which is undefined.

You should simulate a real response object in your tests, e.g.:

var dummyData = {
  data: {
    children: [ 
    { data: 'foo'}
    ]
  }
};
Robin-Hoodie
  • 4,886
  • 4
  • 30
  • 63
  • It worked thank you very much. But I am still confused. As in I am not sure what was the issue and how it got fixed. Can you please elaborate? – Umair Sarfraz Jun 25 '16 at 09:15
  • By using `whenRoute` you're defining what `$httpBackend` will return on a GET to the provided url (so https://www.reddit.com/...), you can read more about it [here](https://docs.angularjs.org/api/ngMock/service/$httpBackend), it's well documented – Robin-Hoodie Jun 25 '16 at 09:18
  • Yes, there's some differences though, which are also specified in the docs – Robin-Hoodie Jun 25 '16 at 09:36
0

You dummyData is not an array, I suppose this could fix the issue, please try with below test

//Testing the getJson function
describe('vm.getJson()', function() {

    it('It should return dummy Data as response and vm.result to be truthy', function() {

        var dummyData = [{ data: 'Umair' }];
        $httpBackend
            .when('GET', 'https://www.reddit.com/r/worldnews/new.json')
            .respond(.respond(
                function() {
                    return [200, dummyData, {}];
                }););

        MainCtrl.getJson();

        $httpBackend.flush();

        expect(MainCtrl.result).toBeTruthy();

    });
});
Muthukannan Kanniappan
  • 2,080
  • 1
  • 16
  • 18
0

You should define undefined variables in component scope:

 beforeEach(async () => {
    fixture = TestBed.createComponent(ExportWizardComponent);
    component = fixture.componentInstance;
    // DEFINE VALUES FOR VARIABLES
    component.patientInfo =  Constants.PROJECT_WIZARD_INFO;
    component.wizardMove = of(null);

    fixture.detectChanges();
  });

  it('should create', async () => {
    expect(component).toBeTruthy();
  });
redrom
  • 11,502
  • 31
  • 157
  • 264