Angular testing HTTP Post calls
Asked Answered
D

1

7

I have an angular component that will POST some data to a URL in our app, and then does nothing else since no data is returned back from that post. I am having trouble testing this since normally HTTP requests are tested by subscribing to the observables they return. This isn't needed to be exposed in this case.

here's my component code:

shareData(): void {
    this.isFinishing = true;
    this.myService.sendSharedData$()
        .pipe(first())
        .subscribe(() => {
            //Data s now shared, send the request to finish up everything
            this.submitFinishRequest();
        }, (e: Error) => this.handleError(e)));
}

private submitFinishRequest(): void {
    //submit data to the MVC controller to validate everything,

    const data = new FormData();
    data.append('ApiToken', this.authService.apiToken);
    data.append('OrderId', this.authService.orderId);

    this.http.post<void>('/finish', data)
        .pipe(first())
        .subscribe((d) => {
            // The controller should now redirect the app to the logged-out MVC view, so there's nothing more we need to do here
            this.isFinishing = false;
        }, (e: Error) => this.handleError(e));
}

And here is my testing code

let component: FinishComponent;
let fixture: ComponentFixture<FinishComponent>;
let myService: MyService;
let httpMock: HttpTestingController;

beforeEach(async(() => {
    TestBed.configureTestingModule({
        imports: [ HttpClientTestingModule ],
        declarations: [ FinishComponent ],
        providers: [ MySerVice ],
    }).compileComponents();
}));

beforeEach(() => {
    fixture = TestBed.createComponent(FinishComponent);
    component = fixture.componentInstance;
    myService = TestBed.get(MyService);
    httpMock = TestBed.get(HttpTestingController);
    sendSharedData$Spy = spyOn(myService, 'sendSharedData$');

    //Add some accounts and shared items to the service for all of these tests 
    accountsService.dataToShare = ['foo', 'bar'];
});

afterEach(() => {
    httpMock.verify();
});

it('should make an HTTP POST to the `/finish` MVC Controller after successfully sharing data', () => {
    sendSharedData$Spy.and.callThrough(); //call through using data provided in `beforeEach`

    fixture.detectChanges(); //triggers ngOnInit()
    component.shareData();
    fixture.detectChanges();

    const req = httpMock.expectOne('/finish');

    expect(req.request.method).toEqual('POST');
    expect(req.request.body).toEqual({
        apiKey: 'api-key-98765',
        orderId: 'order-id-12345'
    });

    //server can send back any data (except for an error) and we would respond the same way, so just send whatever here
    req.flush('');
});

What I actually get in my test is:

Error: Expected one matching request for criteria "Match URL: /finish", found none.

I think this happens because I don't subscribe to the http.post() from inside my test, but if I do doesn't that completely negate the reason I'm testing this method? i shouldn't have to subscribe to things if my methods already do that, right?

Also, when I run this with other tests, another unrelated test usually fails with

Error: Expected no open requests, found 1: POST /finish

Which indicates to me that the request is happening, but at an incorrect time, or I'm not properly waiting on it somehow.


Edit - it's fixed now

The issue was due to the .and.callThrough(). I replaced that with .and.returnValue(of([... some data here ...])); that now it all works as expected. Sorry for the hassle, and thanks for all the help & ideas!

Decline answered 29/4, 2019 at 19:3 Comment(5)
In the actual method I do subscribe in the submitFinishRequest method. I'm just not subscribing in the test since that seems to defeat the purposeDecline
Yes, sorry I misread the code. Why do you test the component and the service all at once, instead of testing just the service, and just mocking it in the component?Hayfield
What does sendShareData do? If it sends an HTTP request, you never tell the test controller what to return and when to flush the response for this first request, so the subscribe callback is never called, so the second finish request is never sent.Hayfield
The user can send data to us, and then once we verify that we got it, we sent another request to the /finish URL to correctly log out. So, this method works, and it subscribes correctly and it does call that private method correctly as well. it just doesn't seem to call the http.post method in time. I've tried adding a fakeAsync and a tick() but that doesn't seem to workDecline
Ugh.. I fixed it. the issue was due to the .and.callThrough(). I replaced that with .and.returnValue(of([... some data here ...])); that now it all works as expected.Decline
L
-1

Try in the test to call the method using your service: myService ["sendSharedData"] ().subscribe(); You use this approach when you want to call private methods. You don't need anymore spy and it should work. I hope :) .

Love answered 29/4, 2019 at 19:40 Comment(3)
Thanks, but this actually does properly call my private method currently. It's just not calling the http.post() method/subscription inside that private method. I've tried waiting on it with a fakeAsync and tick(100) but that doesn't seem to help.Decline
I'm just curious, If you take my approach and put in the req,flush() the data you returned in your spy, doesn't work?Love
I have 2 different data requests going on here. One sends some data off (via a service), and when that observable is successful I then call a 2nd request, just via a plain http.post here (not in a service). I was having trouble testing the 2nd call. The 1st would complete just fine and call the private method, I was just having issues getting it to call the http.post in a testable way. I assumed the way I had it before would make it run as usual, but I needed to have a .and.returnValue() instead of a .and.callThrough() - see my edits at the end of my original questionDecline

© 2022 - 2024 — McMap. All rights reserved.