have written test case for jumping method,
but its not going inside onloadend method seat.onloadend, when I see code coverage report.
in createSpyObj i called loadend but still its not going inside
can you guys tell me how to fix it.
providing my code and test case below.
I am trying to wite test case for each and every line.
jumping(inputValue: any): void {
var that = this;
var file: File = inputValue.files[0];
var seat: FileReader = new FileReader();
seat.onloadend = (e) => {
this.encodeBase64 = seat.result;
that.fileSelect = $("#laptop").val().replace(/^.*\\/, "");
if (that.fileSelect == '') {
that.dragDrop = that.swimming;
} else {
that.dragDrop = "";
that.dragDrop = that.fileSelect;
}
}
$('.running').show();
if (inputValue.files.length > 0) {
var wholeQuantity = 0;
wholeQuantity = inputValue.files[0].size / 1048576; //size in mb
if (wholeQuantity > 5) {
$('.stars').show();
$("#laptop").val('');
this.fileSelect = "";
}
seat.readAsDataURL(file);
}
}
describe('Jasmine Unit Tests: hand-Basketball-Manage-mobiles', () => {
let rainSPORTSService:SPORTSService;
let SPORTSService: SPORTSService;
let decodeService: DecodeService;
let BasketballChainComponent: handBasketballChain;
let kickViewrainsComponent: kickViewrains;
let tiger: Componenttiger<handBasketballChain>;
let raintiger: Componenttiger<kickViewrains>;
let foodktiger: Componenttiger<foodkCarousel>;
let kendotiger: Componenttiger<KendoGridComponent>;
let foodkComponent:foodkCarousel;
let kendoComponent:KendoGridComponent;
beforeEach(async(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
TestBed.configureTestingModule({
imports: [HttpModule, FormsModule,BrowserModule ],
declarations:[handBasketballChain, KendoGridComponent,ProgressCircle,
kickViewrains,handLeftSliderComponent,foodkCarousel,kickmobiles],
providers:[SPORTSService,DecodeService,recentPinnedHistoryService,
{provide: Router, useClass: RouterModule}, validationService,saveService,
ChainService]
}).compileComponents().then(() =>{
foodktiger = TestBed.createComponent(foodkCarousel);
kendotiger = TestBed.createComponent(KendoGridComponent);
foodkComponent = foodktiger.componentInstance;
kendoComponent = kendotiger.componentInstance;
tiger = TestBed.createComponent(handBasketballChain);
BasketballChainComponent = tiger.componentInstance;
SPORTSService = tiger.debugElement.injector.get(SPORTSService);
tiger.componentInstance.kickmobiles.SPORTSService=tiger.debugElement.injector.get(SPORTSService);
tiger.componentInstance.kickViewrains.SPORTSService=tiger.debugElement.injector.get(SPORTSService);
decodeService = tiger.debugElement.injector.get(DecodeService);
BasketballChainComponent.inputfoodkCarousel = foodkComponent; //jasmine.createSpy('foodkCarousel');//.andCallFake(function(msg) { return this });
BasketballChainComponent.kickmobiles.gridkendo=kendoComponent;
})}
));
it('Read kick mobile', (done) => {
let callFirstTime : boolean = true;
let url=
spyOn(BasketballChainComponent.kickmobiles.SPORTSService,'getResponse').and.
callFake(() => {
if(callFirstTime) {
callFirstTime = false; // Invoked by detectChanges()
return Observable.of([{
"mobileId": "100",
"mobileName": "http://localhost:3000/assets/js/actualairings.json",
"mobileType": "TITLE",
"mobileData": "YWZjYXJlZ2Vyamh2dmFyZWdoYnZi",
"notes": "",
"notesId": "100",
"elfDocID": "100",
"url": "http://localhost:3000/upload",
"date": "06/27/2017",
"addedByName": "Kamal",
"userID": "206509786",
"operationType": "create"
}]);
}
});
const fileReaderSpy = jasmine.createSpyObj('FileReader', ['readAsDataURL', 'onloadend']);
spyOn(window, 'FileReader').and.returnValue(fileReaderSpy);
BasketballChainComponent.kickmobiles.jumping({
files: "Untitled-2.txt"
});
var seat = new FileReader();
//seat.onloadend(e);
//BasketballChainComponent.kickmobiles.jumping.onloadend()
tiger.whenStable().then(() => {
done();
});
});
});
Remember, the key to Unit Testing is to write small testable Units of code.Unit Testing - Wikipedia
You're on the right track for the most part, stubbing FileReader and so forth before calling the 'jumping' function. This is very much the way to test code that relies on another external library/function/framework. The relevant portion of the Wikipedia page for Unit Testing states
Because some classes may have references to other classes, testing a class can frequently spill over into testing another class. A common example of this is classes that depend on a database: in order to test the class, the tester often writes code that interacts with the database. This is a mistake, because a unit test should usually not go outside of its own class boundary, and especially should not cross such process/network boundaries because this can introduce unacceptable performance problems to the unit test-suite.
Here's the thing though, when you create your dummy FileReader or mock it never calls 'onloadend' because the mock/stub doesn't have that event and eventing system implemented. This means the mock is incomplete on your part. Wikipedia states
Instead, the software developer should create an abstract interface around the database queries, and then implement that interface with their own mock object. By abstracting this necessary attachment from the code (temporarily reducing the net effective coupling)
In your case, rather than a database, you'd be mocking the FileReader loadend event.
From a test perspective your current code needs a small refactor to become testable. The general objective of Unit Tests is to test small units of functionality in isolation.
The objective in unit testing is to isolate a unit and validate its correctness.
The 'jumping' function relies on a nested arrow function attached to onloadend. Your code has a direct call to that commented out in the test, I'm a little surprised that didn't work to up your code coverage to be honest and would suggest perhaps making sure your code coverage tool, probably Istanbul if you're using Jasmine is configured correctly.
With the above aside, you should refactor that nested function and instead create a named function that you can then call directly for your unit tests.
This is an (untested on my end) example of a better way to implement your function.
jumping(inputValue: any): void {
var that = this;
var file: File = inputValue.files[0];
var seat: FileReader = new FileReader();
// bind the arguments for the event handler, first arg will be 'this' of the
// loaded named function
// second is 'that' variable, seat is seat and the final 'e' variable is
// implicit and shouldn't be specified.
seat.onloadend = loaded.bind(seat, that, seat);
$('.running').show();
if (inputValue.files.length > 0) {
var wholeQuantity = 0;
wholeQuantity = inputValue.files[0].size / 1048576; //size in mb
if (wholeQuantity > 5) {
$('.stars').show();
$("#laptop").val('');
this.fileSelect = "";
}
seat.readAsDataURL(file);
}
}
loaded(that: any, seat: any, e: any): void { // now a testable named function
this.encodeBase64 = seat.result;
that.fileSelect = $("#laptop").val().replace(/^.*\\/, "");
if (that.fileSelect == '') {
that.dragDrop = that.swimming;
} else {
that.dragDrop = "";
that.dragDrop = that.fileSelect;
}
}
An example of a test that will cover all the lines of code of the 'loaded' function as written above is as follows:
describe('test suite', function () {
var old$ = $;
afterEach(function () {
$ = old$;
});
it('covers all lines and else path on if but does not actually test anything', function () {
$ = function () {
val: function () {
return 'Untitled-2.txt';
}
}; // stub JQuery
var seat = {
result: 'Base64encoded'
};
var scope = {};
var that = {
swimming: false,
dragDrop: null
};
BasketballChainComponent.kickmobiles.loaded.call(scope, that, seat, null);
});
it('covers all lines and on if but not else and does not actually test anything', function () {
$ = function () {
val: function () {
return '';
}
}; // stub JQuery
var seat = {
result: 'Base64encoded'
};
var scope = {};
var that = {
swimming: false,
dragDrop: null
};
BasketballChainComponent.kickmobiles.loaded.call(scope, that, seat, null);
});
});
Now please take note, in the real world you should never write tests just for code coverage that do not actually test the given functions. It will lead you into a false sense of security and not actually TEST your code. The MSDN has this to say:
The primary goal of unit testing is to take the smallest piece of testable software in the application, isolate it from the remainder of the code, and determine whether it behaves exactly as you expect.
An analogy of what you're doing would be as follows:
You're working as a car crash tester. Your job is to verify the car is safe in a crash. So a car is crashed at 10 km/h and you need to check it over.
You take a check list of things that you need to confirm. So in a 10 km/h crash you only expect paint to be scratched. So you look at the paint, if the paint is scratched but there's no other damage, the test passes. If the car is dented, the test fails.
That's a good test overall as it is testing something quantifiable and it's testing an intention.
What you're doing by trying to achieve 100% code coverage without actually testing functionality is crashing the car and then not verifying anything.
You're saying "Well I crashed the car, I don't really need to check it did what it's supposed to do in a crash so long as I crashed it right?".
Sure, you got 100% crash coverage by looking at the car, but by not actually testing it, you may as well not have even bothered. Code coverage is a useful tool for spotting code that isn't tested, it's not used to achieve the arbitrary metric of gaining full code coverage. Further reading on this and an excellent write up can be read at Broken promise of 100% code coverage
Essentially the crux of it is
The fact that it is easy to measure code coverage doesn’t make it a good metric though. You can get into trouble even if your code coverage is 100%.
I've omitted the code from the medium article, however it goes on to state:
This unit test produces the perfect 100% test coverage for the elementAtIndex: function.
Does it prove that the function works correctly? The answer is, obviously, no. What happens when we exceed the array boundaries?
Why did that happen? When you try to focus on the code coverage metric you write code that looks at the implementation of the tested function/method. But the implementation is not proven to be correct yet. That is the reason why we want to test it.
Even with that simple function code coverage failed as a good metric to measure the quality of unit-tests.
Further, above I state that you should test the intention of your code. The Medium article also states this.
What to do instead? Don’t look at the actual implementation of the method, look at the contract instead. Look precisely at the outputs of the function/method for any specific inputs. Look at the side-effects that this function does or uses. Take into account the possible edge cases that might exist. List this information and make tests according to that.
Remember
100% code coverage doesn’t mean that your code is 100% correct.
I hope this helps you understand Unit Testing as a concept a little better.
Related
Hey there StackOverflow people of the world! Thank you for helping me with my question, and I apologize if this question gets a bit long winded. I just want to be clear about all the details and constraints I am working with. I found a few other related questions but nothing that was really very clear about how to get around my specific problem, unless I am missing something. Related questions:[1, 2]
Question Setup:
This is what I have and how it works, my question will be about a problem I am having
I've got a object that I've filled with named functions. The purpose of the object map is to contain many functions calls from multiple files. I am calling each function a "business rule" and they are typically very small functions that do a singular action with well-defined inputs and outputs. It also lets me chain the function calls sequentially with the output from functionCall1 becoming the input functionCall2.
All of my business rule definitions up to this point have been in a set of files that reside in a sub-folder called "Framework", but what I am trying to do now is allow the "Client" to define their own business rules in their own files and their own object map of function calls. What I would like to do is add all of the function calls to a single shared data storage.
What I am trying to avoid doing:
I am NOT trying to serialize the function calls, neither am I trying to leverage the 'eval' capability of JS. I've tried working with this before and it gets really messy!
Also I DO NOT want to declare a "class" object or use the "this" keyword for this reason:
10-most-common-javascript-mistakes
What is working:
(NOTE: Greatly simplified as I currently have hundreds of "business rules")
// rulesLibrary.js
import * as stringParsing from './Rules/stringParsing';
export const rulesLibrary = {
['Echo']: (inputData, inputMetaData) => (inputData, inputMetaData),
// Business Rules
// ********************************
// StringParsing rules in order
// ********************************
['stringToBoolean']: (inputData, inputMetaData) => stringParsing.stringToBoolean(inputData, inputMetaData),
['stringToDataType']: (inputData, inputMetaData) => stringParsing.stringToDataType(inputData, inputMetaData),
}
// stringParsing.js
export const stringToBoolean = function(inputData, inputMetaData) {
var returnData;
// Function Body...
return returnData;
};
export const stringToDataType = function(inputData, inputMetaData) {
var returnData;
// Function Body...
return returnData;
};
// ruleBroker.js
import * as rules from './rulesLibrary';
export const processRules = function(inputData, inputMetaData, rulesToExecute) {
var returnData = inputData;
for (var rule in rulesToExecute) {
if (rulesToExecute.hasOwnProperty(rule)) {
var key = rule;
var value = rulesToExecute[key];
returnData = rules.rulesLibrary[value](returnData, inputMetaData);
}
}
return returnData;
};
You can see in the code above the rulesLibrary is defining the functions in an object rulesLibrary = {}; which is also exported. Then in the ruleBroker we are calling the associated function:
rules.rulesLibrary[value](returnData, inputMetaData)....and this works great.
My Goal
My goal is to rather than store all these functionName: functionCall on the rules.rulesLibrary, I want to store them on a singleton data storage object I am calling "D".
Here is the definition of "D":
// data.js
export var data = {};
What I have tried - Attempt 1
I first tried to assign all of the contents of the rules.rulesLibrary from the rulesLibrary.js directly to "D" like so in the ruleBroker.js file:
// NOTE: I am actually doing this inside a function so I can boot-strap the rules.rulesLibrary into `D`, before the application begins going about the business of calling business rules via the ruleBroker.
import * as rules from './rulesLibrary';
var D = require('../Resources/data');
D['BusinessRules'] = {};
D['BusinessRules'] = rules.rulesLibrary;
This did not work and attempting to console.log(JSON.stringify(D)); just gave me back:
D{BusinessRules} = {};
What I have tried -- Attempt 2
So I thought maybe I should try and define the business rules map named function calls directly on "D" like so in the rulesLibrary.js file:
// NOTE: I am again doing all of this inside a boot-strap function for the same reason as above.
export const initRulesLibrary = function() {
D['BusinessRules'] = {};
D['BusinessRules'] = {
['Echo']: (inputData, inputMetaData) => (inputData, inputMetaData),
// Business Rules
// ********************************
// StringParsing rules in order
// ********************************
['stringToBoolean']: (inputData, inputMetaData) => stringParsing.stringToBoolean(inputData, inputMetaData),
['stringToDataType']: (inputData, inputMetaData) => stringParsing.stringToDataType(inputData, inputMetaData),
}
};
Again I get the same thing, contents of D are: D{BusinessRules} = {}.
Maybe console.log in combination with JSON.stringify doesn't work with function-objects?
But then again, I do have rules that return a function-object and I have been able to stringify those function-objects in the past with this same code. Granted it's a function-object so I am not expecting it to look pretty when stringified, but that's not the point. The point should be that the function-object exists on 'D' and it clearly does not, what am I missing here? How can I get all my function-objects mapped on 'D' so that I can add/merge more function-object definitions to it?
Ultimately this is what I want to be able to do:
function addClientRules(clientRules) {
Object.assign(D['BusinessRules'], clientRules['BusinessRules']);
};
Such that D now contains all of the system-defined business rules & all of the client defined business rules. Then in the ruleBroker, I would just call whatever business rule like this:
export const processRules = function(inputData, inputMetaData, rulesToExecute) {
var returnData = inputData;
for (var rule in rulesToExecute) {
if (rulesToExecute.hasOwnProperty(rule)) {
var key = rule;
var value = rulesToExecute[key];
// OLD WAY:
// returnData = rules.rulesLibrary[value](returnData, inputMetaData);
// NEW WAY:
returnData = D['BusinessRules'][value](returnData, inputMetaData);
}
}
return returnData;
};
Any ideas? Thoughts? Edits? Rants? Am I at least on the right track?
Thank you again for your help! Hopefully this will help someone else too!! :-D
Turns out I was already doing everything correctly to begin with. It's just that console.log & JSON.stringify don't work well with a object map of functions.
The function maps do contain the function calls, just don't expect your console.log even with JSON.stringify to dump that data in any way. You have to proceed with making the call as if it is there and verify that the execution is successful by putting console logs in the function that calls the rule and additionally putting console logs in the rule that is to be executed.
It does work and it's pretty cool when it does!!
I hope this can help someone else, please comment if you have any additional questions and/or if I can provide additional solution details.
Log of successful execution:
c.ccustomEcho resolves as: customEcho
BEGIN warden.executeBusinessRule function
businessRule is: customEcho
ruleInput is: Calling Custom Echo from application
ruleMetaData is: Calling Custom Echo from application
BEGIN ruleBroker.processRules function
inputData is: "Calling Custom Echo from application"
inputMetaData is: "something-nothing"
rulesToExecute are: {"0":"customEcho"}
BEGIN clientStringParsing.customEcho function
inputData is: Calling Custom Echo from application
inputMetaData is: something-nothing
returnData is: Calling Custom Echo from application clientStringParsing.customEcho
END clientStringParsing.customEcho function
returnData is: "Calling Custom Echo from application clientStringParsing.customEcho"
END ruleBroker.processRules function
returnData is: Calling Custom Echo from application clientStringParsing.customEcho
END warden.executeBusinessRule function
Cheers
~Seth
when performing unit testing is it better to test the literal result that I expect hard-coding it into the test (expect(x).toBe(17)), or is it better to test the logic and not the specific mock data that I am using (expect(x).toBe(mockedData.value))
The first approach seems safer because I am sure that the test is actually testing the literal result that I expect, however the second approach is more flexible since it allows me to test the logic rather than worry about the mock data (which I can also change later without having to rewrite the test itself)
What are the advantages/disadvantages of either approach? What is the best practice in these cases?
Following is a quick example:
// MockedData is a very long array of complex objects
// each of them has a property 'value' of type number
import mockedData from 'data.mock';
class ClassToTest {
private data;
constructor(data) {
this.data = data;
}
plusOne(): number {
return this.data.value + 1;
}
}
describe('test', () => {
let instance: ClassToTest;
beforeEach(() => {
instance = new ClassToTest(mockedData[0]);
})
it('plusOne() should return the property "value" plus one', () => {
// Should I write this...
expect(instance.plusOne()).toBe(mockedData[0] + 1);
// ...or this?
expect(instance.plusOne()).toBe(17); // Because I know that mockedData[0].value is 16
})
});
Thank you very much!! :)
In your test you want to test your unit, which in your case is the logic inside of your plusOne() function. So you want to only know if something changes inside the function.
The most dangerous path is to use expect(instance.plusOne()).toBe(17);, because if someone changes your logic to return this.data.value + 2;, you will never spot from test only if the problem is in the function logic or in the mockedData.
The less dangerous approach is to use expect(instance.plusOne()).toBe(mockedData[0] + 1);, because this will tell you if the logic in your function change. Still not optimal, since you depend on an external mock to run your test that you don't need. Why would you want to depend on an external mocked data to test your unit?
The best way to test your unit logic here is to do something like this:
describe('test', () => {
let instance: ClassToTest;
const mockedValue = 1;
beforeEach(() => {
instance = new ClassToTest(mockedValue);
})
it('plusOne() should return the property "value" plus one', () => {
expect(instance.plusOne()).toBe(mockedValue + 1);
})
});
Then, you can implement separate tests for your service, here you only test the logic inside plusOne().
I have a javascript function like this
function formatInput(input) {
//want to test only this immediate statement
var type = input.ipType.toString().toLowerCase().trim();
var afterVormat = someFunction(type);
return afterFormat;
}
I am able to test this function(value of afterFormat) correctly , but is it possible/how to test a specific line in function since I am not returning type.
For example I want to test if var type is as it is expected
Is it possible/how to test a specific line in function?
The immediate answer: no.
The solution
One of the outcomes of adhering to TDD is that it forces you to build code in isolated, testable blocks. This is a direct consequence of the fact that you cannot perform test(s) of the individual lines of a function. In your case the solution is to restructure your code to:
var type = function(){
return input.ipType.toString().toLowercase().trim();
};
function formatInput(input) {
var type2 = type();
var afterVormat = someFunction(type);
return afterFormat;
}
Now you have made type an isolated block that you can test.
If you combine this with use of Sinon.JS you can use a spy to test that an invocation of function formatInput() will also result in the invocation of type() and thereby you know for sure that var type2 has been assigned the intended value.
I’m not aware of any specific and more advanced unit testing method/system for javascript, but you can have a simple assertion function to test individual lines of code for debugging purpose like this:
function assert(condition, message) {
if (!condition) {
message = message || "Assertion failed";
if (typeof Error !== "undefined") {
throw new Error(message);
}
throw message; // Fallback
}
}
(Code taken from TJ Crowder's answer to another question.)
Then you can just use it to check for instance the var type like this:
assert(type == "something expected here and shall throw an error otherwise");
You can use console.log() function for that. As below.
function formatInput(input) {
var type = input.ipType.toString().toLowerCase().trim();
console.log(type);
var afterVormat = someFunction(type);
return afterFormat;
}
Also you can use debugger; also, to debug the code line by line.
function formatInput(input) {
var type = input.ipType.toString().toLowerCase().trim();
debugger;
var afterVormat = someFunction(type);
return afterFormat;
}
and just press F10 key to debug the code and you can check the values in console.
I have tried to use Jasmine Matchers and it does give me a great in depth detail about the error messages. The unit tests also become meaningful but when it comes to distributed and large scale projects, I'm not sure how the matchers play a good role.
Below is my matcher example for a sample script.
beforeEach(function () {
jasmine.addMatchers({
toBeAGoodInvestment: toBeAGoodInvestment
});
});
function toBeAGoodInvestment() {
return {
compare: function (actual, expected) {
// Matcher Definition
var result = {};
result.pass = actual.isGood();
if (actual.isGood()) {
result.message = 'Expected investment to be a bad investment';
} else {
result.message = 'Expected investment to be a good investment';
}
return result;
}
}
}
and the spec file is as below
describe('Investment', function () {
var stock, investment;
beforeEach(function () {
stock = new Stock();
investment = new Investment({
stock: stock,
shares: 100,
sharePrice: 20
});
});
it('should be of a stock', function () {
expect(investment.stock).toBe(stock);
});
it('should have invested shares quantity', function () {
expect(investment.shares).toBe(100)
});
it('should have the share paid price', function () {
expect(investment.sharePrice).toBe(20);
});
it('should have a cost', function () {
expect(investment.cost).toBe(2000)
});
describe('when its stock share price valorizes', function () {
beforeEach(function () {
stock.sharePrice = 40;
});
it('should have a positive roi', function () {
expect(investment.roi()).toEqual(1);
});
it('should be a good investment', function () {
expect(investment.isGood()).toEqual(true);
});
it('matcher: should be a good investment', function () {
expect(investment).toBeAGoodInvestment();
});
});
});
I've added both the default Jasmine matchers unit test cases as well as custom matcher. I am unsure how this is helpful in a larger project. If there is any principle guidance behind how to use these matchers then it would be great
I don't think there is a direct and clear correlation between a size of a project and the usefulness of writing custom Jasmine matchers.
Introducing custom Jasmine matchers can have multiple positive impacts on the quality of the test code, its readability and compactness regardless of a size of a codebase:
making a custom matcher for a common assertion check helps not to violate the DRY principle
it is a "Extract Method" refactoring method as well
It is though the author's responsibility to make the matcher provide a clear and understandable feedback in case an assertion error happens. I'm not sure how clear the "good" and "bad" investment rules are in your case, but, on the first glance, I would say errors like:
Expected investment to be a good investment
are not clear enough - compare it to a, for instance:
Expected investment to be a good investment. Shared price is below configured minimum (30).
In other words, when it fails, give as much information as you have, just tell everything.
Another often overlooked point is that custom matcher should be introducing any side effects or change the object under assertion. This may lead to surprises which would be difficult to debug.
In one of our tests, we have the following set of expectations:
expect(headerPage.dashboard.isDisplayed()).toBe(true);
expect(headerPage.queue.isDisplayed()).toBe(true);
expect(headerPage.claimSearch.isDisplayed()).toBe(true);
expect(headerPage.claim.isDisplayed()).toBe(true);
expect(headerPage.case.isDisplayed()).toBe(true);
expect(headerPage.calendar.isDisplayed()).toBe(true);
On one hand, having multiple simple expectations provide a more precise and understandable feedback, but, on another, this looks like it violates the DRY principle and the "one expectation per test" commonly acceptable guideline.
Is there a way to convert/simplify it to a single expect?
headerPage is a Page Object, dashboard and other page object fields are navigation links.
I think you've misunderstood the purpose of the "one expectation per test" guideline. The point isn't to combine a bunch of expectations into a single expectation, it's to split up your expectations into separate tests.
To follow the spirit of that guideline, you would write your tests like this:
describe("The header page", function () {
var headerPage;
beforeEach(function () {
//Common logic here
});
it("displays the dashboard", function () {
expect(headerPage.dashboard.isDisplayed()).toBe(true);
});
it("displays the queue", function () {
expect(headerPage.queue.isDisplayed()).toBe(true);
});
it("displays the claimSearch", function () {
expect(headerPage.claimSearch.isDisplayed()).toBe(true);
});
//etc.
});
That's a fair bit more verbose than what you have; but that's why these are guidelines not rules. It's a tradeoff between how verbose you make your tests, versus how easy they are to debug later. ("The header page displays the dashboard: FAILED") is a very clear and specific test failure message, compared to getting the same failure message regardless of which expectation actually failed.
I definitely would not try to combine all of these lines into a single line. If you don't want to split it into a bunch of different test cases I would just leave it how it is.
Alternative approach. What I've ended up with was to add a page object method that returns the labels of the currently visible navigation links:
this.getVisibleLinks = function () {
return $$(".ap-header-nav-tabs li a").filter(function (link) {
return link.isDisplayed();
}).getText();
};
Then, the above test would be transformed to a concise and readable:
expect(headerPage.getVisibleLinks()).toEqual(["Dashboard", "Queue", "Claim Search", ...]);
If this is logic you are using across multiple specs, you could look into jasmine custom matchers to encapsulate the logic.
It would be written somewhat like this:
var customMatchers = {
toDisplayWidgets: function(util, customEqualityTests) {
return {
compare: function(actual, expected) {
function isDisplayingWidgets(page) {
return page.dashboard.isDisplayed() &&
page.queue.isDisplayed() &&
page.claimSearch.isDisplayed() &&
page.claim.isDisplayed() &&
page.case.isDisplayed() &&
page.calendar.isDisplayed();
}
var result = {};
result.pass = isDisplayingWidgets(actual);
if (!result.pass) {
result.message = 'dashboard is not displayed';
}
return result;
}
}
}
Add the matcher to your current test
jasmine.addMatchers(customMatchers);
And then in your tests you could just assert with
expect(headerPage).toDisplayWidgets();
what about using a helper function that returns results of all the tests, something like
expect(headerDisplayTests()).toBe(true);
function headerDisplayTests() {
return headerPage.dashboard.isDisplayed() &&
headerPage.queue.isDisplayed() &&
headerPage.claimSearch.isDisplayed() &&
headerPage.claim.isDisplayed() &&
headerPage.case.isDisplayed() &&
headerPage.calendar.isDisplayed();
}