I'm trying to unit test an Angular service, that has another service injected into it using $injector, rather than passing the service dependency as an argument. The injected service is dynamic - it could be one of a number of different services - and so it can't be passed as a function argument.
The service under test determines if the application is online or offline, then uses the strategy pattern to either make an HTTP call (we're online, so the strategy is 'remote') or use local storage to get the required data (we're offline, so the strategy is 'local'). It's used in a number of scenarios across the application, so the service and the service method that is subsequently called are dynamic. Easier to show the simplified, relevant, code:
class NetworkStrategyService {
constructor (private Offline, private $injector) {}
fulfill (config) {
this.Offline.check()
.then(
() => {
// we're online
const service = $injector.get(config.remote.service);
service[config.remote.method](config.remote.data)
.then(() => {
// Do something with the data
})
},
() => {
// Grab it from local storage
}
)
}
}
My problem is, because I can't inject the service in the normal way in my unit tests, I don't know how to test this code. I've mocked the config, now I want to test that the correct service methods are being called while online and offline, such as:
it (`calls the remote service, with the remote method, if we're online`, () => {
const remoteMethod = spyOn(config.remote.service, config.remote.method);
// Set up, to get a successful Offline.check() outcome
expect(remoteMethod).toHaveBeenCalledWith(config.remote.data);
});
How can I ensure that the mocked service is a service, and pass it into my service under test?
OK, I seem to have solved it:
let deferred;
let config = {
remote: {
service: 'RemoteService',
method: 'remoteAction',
data: {alpha:1}
},
local: ...
};
beforeEach(() => {
angular.mock.module(($provide) => {
$provide.service(config.remote.service, () => {
return {
[config.remote.method]: () => {}
};
});
});
});
beforeEach(() => {
inject((_$q_, _$rootScope_, _$injector_) => {
$q = _$q_;
$scope = _$rootScope_.$new();
$injector = _$injector_
});
deferred = $q.defer();
});
beforeEach(() => serviceUnderTest = new NetworkStrategyService(Offline, $injector));
it (`calls the remote service, with the remote method, if we're online`, () => {
let dynamicService;
inject((_RemoteService_) => {
dynamicService = _RemoteService_;
});
serviceUnderTest.strategy = 'remote';
const dynamicMethod = spyOn(dynamicService, config.remote.method).and.returnValue(deferred.promise);
serviceUnderTest.makeCall(config);
deferred.resolve();
$scope.$apply();
expect(dynamicMethod).toHaveBeenCalledWith(config.remote.data);
});
Related
I have a TypeScript project which I would like to deploy as JS NPM package. This package performs some http requests using rxjs ajax functions. Now I would like to write tests for these methods.
At some point I have a method like this (simplified!):
getAllUsers(): Observable<AjaxResponse> {
return ajax.get(this.apiUrl + '/users');
}
I know about basic testing, for example with spyOn I can mock a response from the server. But how would I actually test the http request?
The documentation of jasmine says that I cannot do async work in the it part, but in the beforeEach: https://jasmine.github.io/tutorials/async
Would this be the correct approach to test the API?
let value: AjaxResponse;
let error: AjaxError;
beforeEach((done) => {
const user = new UsersApi();
user.getAllUsers().subscribe(
(_value: any) => {
value = _value;
done();
},
(_error: any) => {
error = _error;
done();
}
);
});
it("should test the actual http request", () => {
// Test here something
// expect(value).toBe...
// expect(error).toBe...
});
I couldn't think of another approach how to do the async work...
You need to mock ajax.get to return an Observable that emits values that you want to test.
This is done depending on how ajax is declared in your file that contains user.getAllUsers method.
It'd be ideal if UsersApi() had ajax passed into it (pure function style) because then you could just do something like this:
e.g.
class UsersApi {
public ajax;
constructor(ajax) {
this.ajax = ajax;
}
getAllUsers() {
return this.ajax.get(....)
}
}
Edit: Passing in dependencies (aka dependency injection) is one thing that makes modules like this significantly easier to test - consider doing it!
Then you could very easily mock your tests out like this:
const someSuccessfulResponse = ...
const someFailedResponse = ...
const ajaxWithSuccess = {
get:jest.fn(() => of(someSuccessfulResponse))
}
const ajaxWithFailed = {
get:jest.fn(() => throwError(someFailedResponse))
}
describe('my test suite',() => {
it("should test a successful response", (done) => {
const user = new UsersApi(ajaxWithSuccess);
user.getAllUsers().subscribe(d => {
expect(d).toBe(someSuccessfulResponse);
done();
});
});
it("should test a failed response", (done) => {
const user = new UsersApi(ajaxWithFailed);
user.getAllUsers().subscribe(null,error => {
expect(d).toBe(someFailedResponse);
done();
});
});
});
Note: You DO NOT want to test the actual API request. You want to test that your code successfully handles whatever API responses you think it could receive. Think about it, how are you going to test if a failed API response is handled correctly by your code if your API always returns 200s?
EDIT #27: The above code works fine for me when I run jest, not totally clear on why jasmine (doesn't jest run on jasmine?) says it can't do async in it's. In any case, you could just change the code above to set everything up in the beforeEach and just do your expects in the it's.
using jest to unit test, I have the following line:
jest.mock('../../requestBuilder');
and in my folder, i have a
__mocks__
subfolder where my mock requestBuilder.js is. My jest unit test correctly calls my mock requestBuilder.js correctly. Issue is, my requestBuilder is mocking an ajax return, so I want to be able to determine if I should pass back either a successful or failure server response. Ideally I want to pass a parameter into my mock function to determine if "ajaxSuccess: true/false". How can I do this? Thank you
You don't want to pass a parameter into your mock function, the parameters that are passed to your mock function should be controlled by the piece of code that you are testing. What you want to do is change the mocking behavior between executions of the mock function.
Let's assume that you're trying to test this snippet of code:
// getStatus.js
const requestBuilder = require('./requestBuilder');
module.exports = () => {
try {
const req = requestBuilder('http://fake.com/status').build();
if (req.ajaxSuccess) {
return {status: 'success'};
} else {
return {status: 'failure'}
}
} catch (e) {
return {status: 'unknown'};
}
};
We want to test that getStatus uses the requestBuilder properly, not that the builder.build() method works correctly. Verifying builder.build() is the responsibility of a separate unit test. So we create a mock for our requestBuilder as follows:
// __mocks__/requestBuilder.js
module.exports = jest.fn();
This mock simply sets up the mock function, but it does not implement the behavior. The behavior of the mock should defined in the test. This will give you find grained control of the mocking behavior on a test-by-test basis, rather than attempting to implement a mock that supports every use case (e.g. some special parameter that controls the mocking behavior).
Let's implement some tests using this new mock:
// getStatus.spec.js
jest.mock('./requestBuilder');
const requestBuilder = require('./requestBuilder');
const getStatus = require('./getStatus');
describe('get status', () => {
// Set up a mock builder before each test is run
let builder;
beforeEach(() => {
builder = {
addParam: jest.fn(),
build: jest.fn()
};
requestBuilder.mockReturnValue(builder);
});
// every code path for get status calls request builder with a hard coded URL,
// lets create an assertion for this method call that runs after each test execution.
afterEach(() => {
expect(requestBuilder).toHaveBeenCalledWith('http://fake.com/status');
});
it('when request builder creation throws error', () => {
// Override the mocking behavior to throw an error
requestBuilder.mockImplementation(() => {
throw new Error('create error')
});
expect(getStatus()).toEqual({status: 'unknown'});
expect(builder.build).not.toHaveBeenCalled();
});
it('when build throws an error', () => {
// Set the mocking behavior to throw an error
builder.build.mockImplementation(() => {
throw new Error('build error')
});
expect(getStatus()).toEqual({status: 'unknown'});
expect(builder.build).toHaveBeenCalled();
});
it('when request builder returns success', () => {
// Set the mocking behavior to return ajaxSuccess value
builder.build.mockReturnValue({ajaxSuccess: true});
expect(getStatus()).toEqual({status: 'success'});
expect(builder.build).toHaveBeenCalled();
});
it('when request builder returns failure', () => {
// Set the mocking behavior to return ajaxSuccess value
builder.build.mockReturnValue({ajaxSuccess: false});
expect(getStatus()).toEqual({status: 'failure'});
expect(builder.build).toHaveBeenCalled();
});
});
We are facing an unexpected behavior while testing async code with Jasmine. As far as we know, when you are using the done function, expectations are not called until done is executed. But, that's not happening because the second expectation is failing, hence the $ctrl.todos assignment never happened
Not working test
it('initializes the data when $onIinit', (done) => {
const expected = 'some result';
const response = Promise.resolve(expected);
spyOn(myService, 'getAll').and.returnValue(response);
// This method calls myService.getAll
$ctrl.$onInit();
expect(myService.getAll).toHaveBeenCalled();
expect($ctrl.todos).toEqual(false);
response.then(done);
});
Output: Expected undefined to equal false
On the other hand, this is working:
it('initializes the data when $onIinit', (done) => {
const expected = 'some result';
const response = Promise.resolve(expected);
spyOn(myService, 'getAll').and.returnValue(response);
// This method calls myService.getAll
$ctrl.$onInit();
expect(myService.getAll).toHaveBeenCalled();
response
.then(() => expect($ctrl.todos).toBe(expected))
.then(done);
});
Output: test pass
Controller method:
$ctrl.$onInit = () => {
myService.getAll().then((data) => {
$ctrl.todos = data;
});
};
I found a solution. I don't really need another then. Specs with done will not complete until it's done is called. Also, it should be always placed after expectations.
Working code:
it('initializes the data when $onIinit', (done) => {
const expected = 'some result';
const response = Promise.resolve(expected);
spyOn(myService, 'getAll').and.returnValue(response);
$ctrl.$onInit();
expect(myService.getAll).toHaveBeenCalled();
response
.then(() => {
expect($ctrl.todos).toBe(expected);
done();
});
});
Your aproach seems to be correct, and probably calling done within afterEach will make it works.
afterEach(function(done) {
done();
}, 1000);
But, I would recommend using $httpBackend, the fake HTTP backend implementation suitable for unit testing applications that use the $http service. We are using angularjs anyway, right? So, why not take advantage of?
With $httpBackend we can make the requests, then response with mock data without really sending the request to a real server. Here an ilustrative example
it('initializes the data when $onIinit', () => {
const mockData = { data: { expected: 'some result' } };
spyOn(myService, 'getAll').and.callThrough();
$httpBackend.expect('POST', '/my-service/url').respond(200, mockData);
// This method calls myService.getAll
$ctrl.$onInit();
//flush pending request
$httpBackend.flush();
expect(myService.getAll).toHaveBeenCalled();
expect($ctrl.todos).toBeDefined();
});
Some explanations, $httpBackend.expect('POST', '/my-service/url'), here note that 'POST' need to match the method used by your service in myService.getAll, and '/my-service/url' is the url also used by your service in myService.getAll.
It is required to call $httpBackend.flush();, it will release all pending requests.
You will need to inject $httpBackend into your tests, an easy way would be
describe('$httpBackend service in module ngMock', () => {
let $httpBackend;
beforeEach(inject(['$httpBackend', (_$httpBackend) => {
$httpBackend = _$httpBackend;
}]));
it('$httpBackend is defined', () => {
// here we can use $httpBackend
expect($httpBackend).toBeDefined();
});
});
Also, note that $httpBackend is part of the ngMock module.
More info about testing angularjs code here
Hope it helps
Promise is from outside Angular world - you have to wait for result that will be available in next event queue tick - dirty (almost dirty) hack is to use setTimeout
angular.module('test', [])
.controller('underTest', function($scope, myService) {
$scope.$onInit = function() {
myService.getAll().then(function(data) {
$scope.todos = data
})
}
})
describe('Controller: somethingChanged', function() {
var scope, myService
beforeEach(function() {
module('test')
})
beforeEach(function() {
module(function($provide) {
$provide.value('myService', {
getAll: function() {}
})
})
})
beforeEach(inject(function($controller, _$rootScope_, _myService_) {
myService = _myService_
scope = _$rootScope_.$new()
$controller('underTest', {
$scope: scope
})
}))
it('initializes the data when $onIinit', function(done) {
const expected = 'some result'
const response = Promise.resolve(expected)
spyOn(myService, 'getAll').and.returnValue(response)
scope.$onInit()
expect(myService.getAll).toHaveBeenCalled();
setTimeout(function() {
expect(scope.todos).toEqual(expected)
done()
})
});
});
<script src="https://cdn.jsdelivr.net/jasmine/2.6.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/2.6.1/jasmine-html.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/2.6.1/boot.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/jasmine/2.6.1/jasmine.css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-mocks.js"></script>
In the example provided in the aor-realtime readme
import realtimeSaga from 'aor-realtime';
const observeRequest = (fetchType, resource, params) => {
// Use your apollo client methods here or sockets or whatever else including the following very naive polling mechanism
return {
subscribe(observer) {
const intervalId = setInterval(() => {
fetchData(fetchType, resource, params)
.then(results => observer.next(results)) // New data received, notify the observer
.catch(error => observer.error(error)); // Ouch, an error occured, notify the observer
}, 5000);
const subscription = {
unsubscribe() {
// Clean up after ourselves
clearInterval(intervalId);
// Notify the saga that we cleaned up everything
observer.complete();
}
};
return subscription;
},
};
};
const customSaga = realtimeSaga(observeRequest);
fetchData function is mentioned, but it's not accessible from that scope, is it just a symbolic/abstract call ?
If I really wanted to refresh the data based on some realtime trigger how could i dispatch the data fetching command from this scope ?
You're right, it is a symbolic/abstract call. It's up to you to implement observeRequest, by initializing it with your restClient for example and calling the client methods accordingly using the fetchType, resource and params parameters.
We currently only use this saga with the aor-simple-graphql-client client
I'm new to unit testing in Node and hit a road block when it comes to promises. My module performs a search through the ApiController which then returns a promise. Depending on its resolve status, it invokes one of two private functions within the module.
Module:
module.exports.process = function(req, res) {
let searchTerm = req.query.searchValue;
ApiController.fetchResults(searchTerm)
.then((results) => onReturnedResults(results), (err) => onNoResults());
function onReturnedResults(results) {
req.app.set('searchResults', results);
res.redirect('/results');
}
function onNoResults() {
res.render('search/search', { noResultsFound: true });
}
};
Test:
var res = {
viewName: '', data : {},
render: function(view, viewData) {
this.view = view;
this.viewData = viewData;
}
};
var req = { query: { searchValue: 'doesnotexist' } }
describe('When searching for results that do not exist', function() {
it('should display a no results found message', function(done) {
let expectedResult = {
noResultsFound: true;
}
SearchController.process(req, res);
// Assert...
expect(res.viewData).to.be.equal(expectedResult);
done();
});
})
What are the best practices around this and how can I 'mock' the fetchResults returned promise so that it doesn't actually fetch results from the API (whilst still invoking the private function)?
If methods are scoped as closures or are private to modules, then there is no way to access them and can only be verified indirectly. To provide a test implementation of fetchResults it has to be exposed in some way.
One way is to require ApiController (this may already be happening) and then use proxyquire or mockery to override the dependency. I've worked with many test suite, who's only way to interact and provide test dependencies are through patching require. Test suites become a nightmare using this method. Cleanup, unpatching, cascading patches, all become huge time sinks, and would recommend against it if there are any other options.
Another way is to bake in the ability to change fetchResults implementation into your objects. In addition to testing, making fetchResults configurable will help insulate your code from future changes, by helping to minimize the impact to process when/if the way you need to fetch results changes:
var Results = (function() {
return {
process: function(req, res) {
let searchTerm = req.query.searchValue;
this.fetchResults(searchTerm)
.then((results) => this.onReturnedResults(req, res, results), (err) => this.onNoResults(res)),
fetchResults: ApiController.fetchResults,
onReturnedResults: function(req, res, results) {
req.app.set('searchResults', results);
res.redirect('/results');
},
onNoResults: function(res) {
res.render('search/search', { noResultsFound: true });
}
};
})()
(i haven't used javascript in a while, so i'm not sure of the best way to model the above object, but the important part is that if your test wants to override an implementation than the implementation has to be exposed, which the above code should do)
Now there is a Results object with the fetchResults method exposed in a way that's easily overridable
var fakeFetchResults = sinon.stub();
fakeFetchResults.return(aFakePromiseWithResultsToTriggerResultsCodePath);
Results.fetchResult = fakeFetchResults;
The above exposes the return function and the error function, which also allow for isolated unit tests of that functionality.
Using process is done through the results object, your controller could register
Results.process