2016-08-27 29 views
5

Mam node.js aplikację przy użyciu Express 4 i to jest mój kontroler:Jak przetestować funkcję, która wywołuje inną, która zwraca obietnicę?

var service = require('./category.service'); 

module.exports = { 
    findAll: (request, response) => { 
    service.findAll().then((categories) => { 
     response.status(200).send(categories); 
    }, (error) => { 
     response.status(error.statusCode || 500).json(error); 
    }); 
    } 
}; 

To nazywa moja usługa, która zwraca obietnicę. Wszystko działa, ale mam problemy podczas próby przetestowania go.

Zasadniczo chciałbym się upewnić, że na podstawie tego, co moja usługa zwróci, przepłucz odpowiedź odpowiednim kodem stanu i treścią.

Więc z mokka i sinon Wygląda to mniej więcej tak:

it('Should call service to find all the categories', (done) => { 
    // Arrange 
    var expectedCategories = ['foo', 'bar']; 

    var findAllStub = sandbox.stub(service, 'findAll'); 
    findAllStub.resolves(expectedCategories); 

    var response = { 
     status:() => { return response; }, 
     send:() => {} 
    }; 
    sandbox.spy(response, 'status'); 
    sandbox.spy(response, 'send'); 

    // Act 
    controller.findAll({}, response); 

    // Assert 
    expect(findAllStub.called).to.be.ok; 
    expect(findAllStub.callCount).to.equal(1); 
    expect(response.status).to.be.calledWith(200); // not working 
    expect(response.send).to.be.called; // not working 
    done(); 
}); 

Ja testowałem moje podobne scenariusze, gdy funkcja I 'm testowania sam powróci obietnicę ponieważ mogę hak moje twierdzenia w czym.

Próbowałem również zawinąć controller.findAll z Obietnicą i rozwiązać go z response.send, ale to też nie zadziałało.

+1

Każda funkcja, która wywołuje funkcję zwracającą obietnicę, jest asynchroniczna i powinna sama zwrócić obietnicę. Jeśli odbierasz tylko wywołania zwrotne, musisz wrócić do testowania interfejsu API opartego na oddzwanianiu. – Bergi

+0

Czy używasz Chai? Jeśli tak, to http://chaijs.com/plugins/chai-as-promised/ –

+0

Tak, ale funkcja, którą testuję, nie zwraca obietnicy o łańcuchu synów, ponieważ obiecany nie pomaga w zwykłym – jbernal

Odpowiedz

6

Należy przenieść punkt wymuszenia w metodzie res.send, aby upewnić się wszystkie zadania asynchroniczne są wykonywane przed twierdzeniami:

var response = { 
    status:() => { return response; }, 
    send:() => { 
    try { 
     // Assert 
     expect(findAllStub.called).to.be.ok; 
     expect(findAllStub.callCount).to.equal(1); 
     expect(response.status).to.be.calledWith(200); // not working 
     // expect(response.send).to.be.called; // not needed anymore 
     done(); 
    } catch (err) { 
     done(err); 
    } 
    }, 
}; 
+0

Tak, teraz to działa. Jeśli jednak spróbuję testu, to się nie uda (porównaj status do 204, powiedzmy), nie pokazuje nieudanego stwierdzenia. Po prostu przekracza limit czasu i mówi: 'Błąd: przekroczono limit czasu 2000ms. Upewnij się, że wywołanie metody done() jest wywoływane w tym teście. " – jbernal

+0

@jbernal my bad, zapomniałem, że musisz złapać zgłoszony błąd i przekazać go do' done', patrz poprawiona odpowiedź –

+0

Dziękuję bardzo. Używanie bloków try/catch mnie uratowało. – phillyslick

1

Chodzi o to, aby otrzymać obietnicę, która service.findAll() powraca dostępna w kodzie testu bez wywoływania service. O ile widzę sinon-as-promised, którego prawdopodobnie używasz, nie pozwala na to. Więc użyłem natywnego Promise (mam nadzieję, że twoja wersja węzła nie jest za stara).

const aPromise = Promise.resolve(expectedCategories); 
var findAllStub = sandbox.stub(service, 'findAll'); 
findAllStub.returns(aPromise); 

// response = { .... } 

controller.findAll({}, response); 

aPromise.then(() => { 
    expect(response.status).to.be.calledWith(200); 
    expect(response.send).to.be.called;  
}); 
+0

Znam ten scenariusz. Ale jeśli zdasz sobie sprawę, mój 'controller.findAll' nie zwróci obietnicy, nie musi tego robić. A więc mój problem. Nie mogę wykonać w nim wtedy. – jbernal

+0

@jbernal, widzę twój punkt widzenia. Zmiksowałem 'findAll's. Czy to nie pomoże, jeśli wywołasz 'controller.findAll()' i po tym łańcuchu 'service.findAll()', to jest 'constoller.findAll(); service.findAll(). Then (() => { oczekiwać (...) }); ' –

+0

Nie sądzę, aby ..." controller.findAll "jest jeden nazywający' service.findAll' i to jest jedna z badanych rzeczy, więc nie chcę tego wyraźnie nazywać. – jbernal

1

Kiedy kod jest trudny do testowania może to oznaczać, że nie może być inaczej możliwości projektowania, które ułatwiają testowanie. Wyskakuje to, że service jest zamknięty w module, a zależność nie jest w ogóle widoczna. Czuję, że celem nie powinno być znalezienie sposobu na przetestowanie kodu tak jak jest, ale znalezienie optymalnego projektu.

IMO Celem jest znalezienie sposobu na wyeksponowanie service, aby Twój test mógł zapewnić implementację kodu krótkiego, tak aby logika findAll mogła być testowana w izolacji, synchronicznie.

Jednym ze sposobów jest użycie biblioteki takiej jak mockery lub rewire. Oba są dość łatwe w użyciu (z mojego doświadczenia wynika, że ​​kpina zaczyna się pogarszać i staje się bardzo trudna w utrzymaniu w miarę wzrostu zestawu testów i liczby modułów). Pozwalają one na łatanie var service = require('./category.service'); poprzez dostarczenie własnego obiektu usługowego z własnym zdefiniowanym findAll zdefiniowanym .

Innym sposobem jest rearchitekcja kodu, aby w pewien sposób odsłonić dzwoniącemu numer service. Umożliwi to Twojemu rozmówcy (testowi urządzenia) dostarczenie własnego kodu pośredniczącego service.

Jednym z łatwych sposobów na to jest eksportowanie konstruktora funkcji zamiast obiektu.

module.exports = (userService) => { 

    // default to the required service 
    this.service = userService || service; 

    this.findAll = (request, response) => { 
    this.service.findAll().then((categories) => { 
     response.status(200).send(categories); 
    }, (error) => { 
     response.status(error.statusCode || 500).json(error); 
    }); 
    } 
}; 

var ServiceConstructor = require('yourmodule'); 
var service = new ServiceConstructor(); 

Teraz test może tworzyć zalążek dla service i dostarczyć go do ServiceConstructor wykonywać metodę findAll. Całkowite usunięcie potrzeby testu asynchronicznego.

+0

Dziękuję bardzo za odpowiedź. To są zdecydowanie pomysły, które muszę przeanalizować. Pracuję nad moją pierwszą aplikacją węzła i jestem pewien, że istnieją bardziej optymalne i lepiej zorganizowane wzorce, które mogę zastosować, aby ulepszyć mój kod. Spróbuję je wdrożyć. Twoje zdrowie – jbernal

Powiązane problemy