So for a while now I've been pondering what a real unit test should consist of. It's best to demonstrate this with an example, here's my angular service I want to test:
function stringUtils() {
return {
splitFilterString: splitFilterString
}
function splitFilterString (filterString) {
return filterString.split(':');
}
}
I've been thinking which of my 2 approaches best describes a "real" unit test for splitFilterString. (The examples are written with jasmine)
1 Test that String.prototype.split has been called.
You don't test any "real" examples with this, you just test that the function actually calls String.prototype.split and returns whatever that function returns. This has been my way of testing so far because you don't test "external" API's for doing what they tell you they (should) be doing.
it('should call split with ":" as an argument and return whatever split returns', function () {
var filterString = 'foo:bar';
spyOn(String.prototype, 'split').and.returnValue('foo');
expect(stringUtils.splitFilterString(filterString)).toBe('foo');
expect(String.prototype.split).toHaveBeenCalledWith(':');
});
2 Test for the actual expected output of the function.
I also like this approach because you test for actual input/output how the function is meant to be used. On the downside you're indirectly testing String.prototype.split here as well, you're testing if it actually does what it says it does.
it('should return the correct output', function () {
expect(stringUtils.splitFilterString('foo:bar')).toEqual(['foo', 'bar']);
expect(stringUtils.splitFilterString('foobar')).toEqual(['foobar']);
});
In the first example you are coupling the test with the implementation, that's the main difference.
This might get in your way later on if you find out that there's a superFastSplit that you want to use, because you need to change your code and your test.
So in this case the test is not providing real value because it might hold you back when you want to refactor: "I want to refactor that portion of code, but those damn tests mean I have to do everything twice!".
In the second example you are testing behaviour, so your code will allow you to use superFastSplit because you are not interested in how you get to your result, you are interested that the result is consistent across implementations.
EDIT After OP comment
In the case of an external API I would still follow the "don't harm my future self / colleague" path, so I would do the easiest thing, which IMHO is to use a module that mocks the external API, something like this:
nock('http://external.api.com')
.get('/end/point')
.reply(200);
Of course you have to be careful and don't try to cover too many scenarios, because you are basically deciding what the external API will return, so I would say I would just test the ok and nok scenarios here, and cover all the details in an integration test.
Under the covers what nock does is:
Nock works by overriding Node's http.request function. Also, it overrides http.ClientRequest too to cover for modules that use it directly.
You can always test for any side effect that the API call has in your code, but I feel that that approach is more difficult to follow when applying TDD.
Related
I need to skip all it() if beforeAll() failed, but all solutions that I found don't work - beforeAll() throw an error but all of it() still executes and gets marked as 'failed'
I've tried:stopSpecOnExpectationFailure: true, and Jasmine pending() option but those still do not work.
Does this solution fits your needs?
https://www.npmjs.com/package/protractor-fail-fast
or
https://github.com/pmowrer/jasmine-fail-fast
I know that suggest npm-packages is not a real 'answer' but I've been seeing this type of questions every couple of months and it usually ends up with using some ready and working solution (like above)
Update: I will appreciate if you will come up with your own solution and share it
Update 2: Also I will share one wacky way you can do it.
You will need Protractor 6 (because it uses latest jasmine version)
Let's say you tests are depending on the presences of some element.
You can do this in your beforeAll :
let elementIsPresent = await myElement.isDisplayed()
it('should test if element is present', function() {
if(elementIsPresent) {
// do your thing
} else {
pending('skipping test')
}
});
You need to be aware of a couple of thing:
Protractor below version 6 will mark this test as 'failed' (instead skipping)
I can not use arrow functions in your it blocks
One of my angularjs files had a code construct similar to the below:
this.examplecall = function() {
var a = apiconfig.getconfig();
....
//some lines of code
....
var b = apiconfig.getconfig();
}
And I started to write unit tests for it through angular spec - Jasmine, ended up with the below stub of code.
describe("test examplecall")...
it("should test cofig call in examplecall", function() {
$httpBackend.expectGET(GET_CONFIG).respond(200);
});
The above code throws exception telling "unexpected GET.."
When I added an extra expectGET, things worked out fine. See below:
describe("test examplecall")...
it("should test cofig call in examplecall", function() {
$httpBackend.expectGET(GET_CONFIG).respond(200);
$httpBackend.expectGET(GET_CONFIG).respond(200);
});
From this, I infer that if there are two api calls in a particular function, then I have to expect it two times.
Does that mean, if there are n same api calls in a particular code stub, I have to expect them n number of times ?
Are there any similar constructs like,
$httpBackend.WheneverexpectGET(GET_CONFIG).respond(200);
so, whenever we call a API just return 200 status like above?
Thank you for your comments on this...
EDIT: (read the accepted answer before going through this.)
Thanks to #kamituel for the wonderful answer.
To summarise with the information provided in his answer:
Use of expect :
Expects the order of the API call. It expects that the code should call the api's in the exact order that you expect.
If there are 3 api calls, then you should expect them 3 times.
Use of when : ($httpBackend.when)
Does not expect if an API call is made or not. Just doesn't throw any error.
$httpBackend.when behaves like a mini mock database. Whenever your code, expects some response from an API, supply it. Thats it.
Yes, .expectGET is used to assert that a given request has been made by the application. So you need to call it n times if you expect the application to make n requests.
If you don't need to assert on that, but only want to make application logic work through any requests it makes, you might want to use .whenGET instead. Difference between .expectXXX and .whenXXX has been already described in another answer.
Edit: not sure which Angular version are you using, but you will find this in the implementation of .expectGET:
expectations.shift();
This is invoked once a request is made and matches what was expected. Which means the same expectation is only asserted on once.
It's usually also a good idea to call .verifyNoOutstandingExpectation() after your test is done, to ensure that each request you specified as epxected using .expectXXX() has indeed been made by the application.
I understand that taking time and thinking to write tests with proper assumptions is important. However something like this can serve as starting boilerplate code.
For example , consider a function
function sum(a,b) {
return a+b;
}
console.log(sum(3,4));
console.log(sum(0,2));
so we inject/include a function , i.e it listens
in on the input given to sum while it is running(like dojo connect, listens in on the arguments object for expected inputs and the result of the function for the expected output) and auto generates the test cases.
so the tool will automatically generate tests as
describe("sum tests", function(){
SpyOn(sum);
expect(sum(3,4).to.Equal(7));
expect(sum(0,1).to.Equal(1))
})
If something like this doesn't exist , I was thinking to attempt to build a basic version of this myself. I guess for complex inputs as objects ,may need to create spies for function call. ex -
sum.apply(null, Source.getInputs()).
so now need to mock the Source.getInputs function.
What other complications I may need to take into account in that case ?
I am using protractor to test a series of webpages (actually mostly one webpage of Angular design, but others as well). I have created a series of page objects to handle this. To minimize code maintenance I have created an object with key value pairs like so:
var object = {
pageLogo: element(by.id('logo')),
searchBar: element.all(by.className('searchThing')),
...
};
The assumption being that I will only need to add something to the object to make it usable everywhere in the page object file. Of course, the file has functions (assuming you are not familiar with the page object pattern) as such:
var pageNamePageObject = function () {
var object = {...}; //list of elements
this.get = function() {
brower.get('#/someWebTag');
}
this.getElementText = function(someName){
if (typeof someName == 'number')
... (convert or handle exception, whatever)
return object[name].getText();
}
...
*Note these are just examples and these promises can be handled in a variety of ways in the page object or main tests
The issue comes from attempting to "cycle" through the object. Given that the particular test is attempting to verify, among other things, that all the elements are on the particular web page I am attempting to loop through these objects using the "isPresent()" function. I have made many attempts, and for brevities sake I will not list them here, however they include creating a wrapper promise (using "Q", which I must admit I have no idea how it works) and attempting to run the function in the 'expect' hoping that the jasmine core will wait for all the looping promises resolve and then read the output (it was more of a last ditch effort really).
You should loop like you did before on all of the elements, if you want it in a particular order, create a recursive function that just calls itself with the next element in the JSON.
Now, to handle jasmine specs finishing before and that stuff.
this function needs to be added to protractor's flow control for it to wait to continue, read more about it here. and also, dont use Q in protractor, use protractor's implementation of webdriverJS promises.
Also, consider using isDisplayed instead, assuming you want it to be dispalayed on the page.
So basically, your code skeleton will look like this:
it(.....{
var flow = webdriver.promise.controlFlow();
return webdriver.execute(function () {//your checks on the page here,
//if you need extract to external function as i described in my first paragraph
well i think that should provide you enough info on how to handle waiting for promises in protractor, hope i helped.
I have a Backbone app written in CoffeeScript. I'm trying to use Mocha (with Chai and Sinon) to write tests for DOM-related behaviors, but it seems that hidden fixtures (I'm using js-fixtures now but I've also tried this unsuccessfully with a hidden '#fixtures' div) don't register certain DOM-related behaviors which makes testing certain types of DOM-related behaviors (seemingly) impossible.
For example, my main app view has several subviews which are never rendered at the same time: when the app view renders subview A, it remembers the focused element of the currently active subview B (#_visibleView), saves that information on subview B, closes the subview B, and then renders subview A.
_rememberFocusedElement: ->
focusedElement = $ document.activeElement
if focusedElement
focusedElementId = focusedElement.attr 'id'
if focusedElementId
#_visibleView?.focusedElementId = focusedElementId
This works when I test it manually, but when I try to write unit tests for this behavior they fail because I can't set focus (e.g., via $(selector).focus()) to an element in a hidden div/iframe. (I have the same issue with functionality which listens for window resize events.)
I thought that if I changed $ document.activeElement to #$ ':focus" I might get different results, but that doesn't fix the issue.
Here is what the relevant parts of my Mocha (BDD) tests look like. This spec will print TEXTAREA to the console and then undefined, indicating that there is a textarea with id='transcription' but I can't set focus to it.
beforeEach (done) ->
fixtures.path = 'fixtures'
callback = =>
#$fixture = fixtures.window().$ "<div id='js-fixtures-fixture'></div>"
#appView = new AppView el: #$fixture
done()
describe 'GUI stuff', ->
it 'remembers the currently focused element of a subview', (done) ->
#appView.mainMenuView.once 'request:formAdd', =>
#appView._visibleView.$('#transcription').focus()
console.log #appView._visibleView.$('#transcription').prop 'tagName'
console.log #appView._visibleView.$(':focus').prop 'tagName'
done()
#appView.mainMenuView.trigger 'request:formAdd'
Is there any way that I can write unit tests for these types of behaviors?
Ok, first off let me clarify something: the term "unit test" means man different things to many people. Often times it becomes synonymous with "any test written using a unit test framework (like Mocha)". When I use the term "unit test" that's not what I mean: what I mean is a test that tests only a single unit of work (which, in a JS environment, will usually be a single function, but might be a whole class).
Ok, with that out of the way, if you really are trying to unit test your code, you're sort of taking the wrong approach. A unit test really shouldn't rely on anything outside the context of the function being tested, and so relying on the (external) DOM is where your problem lies.
Let's assume your focus-handling code is in a function called handleFocus (I don't know the actual method name). Consider the following test, which I'll write using JavaScript since my CoffeScript is rusty:
describe('#handleFocus', function() {
it('remembers the currently focused element of a subview', function() {
var setFocusStub = sinon.stub($.fn, 'focus');
appView._visibleView.handleFocus();
expect(setFocusStub.calledOnce).to.be(true);
});
});
The above is a bit of an over-simplification, but hopefully it illustrates the point. What you're really trying to check isn't whether the DOM (fake or real) does X; what you're trying check is whether your function does X. By focusing on that in your test, and relying on a stub that checks whether "X" happened or not, you completely eliminate the need for the DOM to be involved.
Now of course you might wonder: "well great, that helps me in test-land, but how do I know it will work in a real environment?" My answer to that would be that your (probably Selenium-based) acceptance tests should cover that sort of thing. Acceptance tests should check whether your overall code works in the real world, while unit tests should ensure that individual pieces of that code work in a fake environment.
The former is great for ensuring your customers don't see bugs, while the latter is great for figuring out exactly where those bugs come from, and for making it possible for you to refactor safely.