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.
Related
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().
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.
I am writing unit test cases using karma-jasmine for my angular application. I have a configuration constant something like this:
app.constant('Configuration', {
BASE_URL: 'http://localhost:3030',
SUPPORTED_BROWSERS: ['Chrome', 'Firefox', 'CriOs']
});
I need to test the unit test the Configuration constant. I don't know whether it makes sense or not testing a constant. My suits will be something like this:
describe('Configuration.SUPPORTED_BROWSERS', function() {
it('should say only 3 browsers are supported', function() {
expect(Configuration.SUPPORTED_BROWSERS.length).toBe(3);
});
it('should say crios is supported', function() {
var temp = Configuration.SUPPORTED_BROWSERS.some(function(browser) {
return browser.toLowerCase() === 'chrome';
});
expect(temp).toBe(true);
});
it('should say firefox is supported', function() {
var temp = Configuration.SUPPORTED_BROWSERS.some(function(browser) {
return browser.toLowerCase() === 'firefox';
});
expect(temp).toBe(true);
});
it('should say chrome is supported', function() {
var temp = Configuration.SUPPORTED_BROWSERS.some(function(browser) {
return browser.toLowerCase() === 'crios';
});
expect(temp).toBe(true);
});
});
I am not sure whether it is good to test something which is constant, does not contain any logic and is hard coded.
NO , Its not a good practice to write unit test cases for constants . Unit testing mainly done for the functionality and logic that it is working fine or not . For a constant there is no logic to check .
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();
}
I have often heard developers say that test code should be 'ugly', and plain as possible.
The reason for that, being that any logic in the test, needs to be tested it self, and it creates and chicken and egg paradox.
I found myself making my test much more readable, structured and test code much more reusable by using some simple logic.
The question is, are those valid unit tests?
I am using karma as a test runner, this specific project uses a node server with connect-asset-manager, and bower packages for front end.
How can I make the code more modular(AMD? browserify?), without having to implement everything from scratch or to introduce a framework.
Currently, global state is changed across many files, and many closures, I don't like it.
How can I make code within closure still testable? this package perhaps?
Example code:
utils.js
function getRealTemplatValues (inputs, templateFn, outerId, innerClass) {
var i, res;
for (i = 0; i < inputs.length; i++) {
res = templateFn(inputs[i]);
$('#' + outerId).append(res);
}
return $('.' + innerClass).map(function(){
return $(this).html();
}).get();
};
function assertEqual (done, expect, inputs, outputs, fn, decorator) {
function iter (input, output, cb) {
var i;
for (i = 0; i < inputs.length; i++) {
cb(input[i], output[i]);
}
};
function cb (input, output) {
output = !!decorator ? decorator(output) : output;
expect(fn(input, decorator)).toBe(output);
done && done();
}
iter(inputs, outputs, cb, decorator);
};
helper.scenario.js
describe('helpers', function () {
var inputs = ["#string#string#string", "string#string", "string#string#string#", "#", "###"],
outputs = ["#string #string #string", "string #string", "string #string #string#", "#", "###"],
decorString = 'magic!',
outerId = "container",
innerClass = "inner",
decorator = function(input) {return input + decorString};
describe('breakHashtags - unit', function(done) {
it('should break hashtags with prefixed with spaces - non decorated', function (done) {
return assertEqual(done, expect, inputs, outputs, breakHashtags);
});
it('should break hashtags with prefixed with spaces - decorated', function () {
return assertEqual(done, expect, inputs, outputs, breakHashtags, decorator);
});
});
describe('handle bars integration', function () {
var outerId = "container",
outerClass = "inner",
fixture = '<div id="' + outerId + '"></div>',
template = Handlebars.compile('<div class="' + outerClass + '">{{breakHashtags hashtags}}</div>'),
decoratedTemplate = Handlebars.compile('<div id="inner">{{breakHashtags hashtags decoratorHelper}}</div>');
beforeEach( function () {
addFixture(fixture);
});
afterEach( function () {
clearMyFixtures();
Handlebars.helpers['decoratorHelper'] && delete Handlebars.helpers['decoratorHelper'];
});
it('should have the breakHashtags function registered as a helper', function () {
expect(Handlebars.helpers['breakHashtags']).toEqual(breakHashtags);
});
it('should replace hashtags with hashtags prefixed with spaces', function(){
var realValues = getRealTemplatValues(inputs, template, outerId, outerClass);
assertEqual(done, expect, inputs, realValues, breakHashtags);
});
it('should replace hashtags with hashtags prefixed with ' +
'spaces and invoke the decorator on theo put', function(){
Handlebars.registerHelper('decoratorHelper', decorator);
var realValues = getRealTemplatValues(inputs, decoratedTemplate, outerId, outerClass);
assertEqual(done, expect, inputs, realValues, breakHashtags, decorator);
});
});
});
helpers.js:
function breakHashtags (text, decorator) {
var pattern = /\w(#).+/i,
p1, p2, idx = text.search(pattern), prefix = '';
while (idx > 0) {
if (idx === 1) {
text = text.substring(idx);
prefix = text.substring(0, 1);
}
else{
p1 = text.substring(0, idx + 1);
p2 = text.substring(idx + 1);
text = p1 + ' ' + p2;
console.log(p1, p2, text)
}
idx = text.search(pattern);
}
return !!decorator ? decorator(prefix + text) : prefix + text;
}
Handlebars.registerHelper('breakHashtags', breakHashtags);
my opinion:
i think you went way too far. for example:
it('should replace hashtags with hashtags prefixed with spaces', function(){
var realValues = getRealTemplatValues(inputs, template, outerId, outerClass);
assertEqual(done, expect, inputs, realValues, breakHashtags);
});
it may be more readable to you but only to you. programming is a team game. when someone else sees that code first time, he has no idea what's going on.
there is a description in that test but it has nothing to with what you actually test. don't know exactly what your code does but tests should look something like this:
it('should replace hashtags with hashtags prefixed with spaces', function(){
var result = testedFunction("#hashtag1#hashtag2#hashtag3");
assertThat(result).isEqualTo("#hashTag1 #hashTag2 #hashTag3");
});
Now each test is a whole on it's own. and no one has to check different files to understand it. what if someone change your helper functions? will you notice it?
How can I make the code more modular(AMD? browserify?), without having
to implement everything from scratch or to introduce a framework.
what's wrong with framework? after all you've just started to write your own. it's better to use 3rd party framework because everyone in your team knows what exactly it does. when you write your own helpers, everyone has to learn them from scratch.
furthermore, it's better to write helpers that makes your tests more powerful in a general way. feel free to create simple framework for parametrized testing or general purpose assertions. it's perfectly clear how it works, what it does, when and how it should be changed. so you can use them in all your tests
but when you create functions like assertEqual (done, expect, inputs, outputs, fn, decorator) then no one knows what it does. why the hell assertEquals has so many parameters? it should have at most 2-3 (actual, expected, error message). so can i change that assertEquals when i need? or should i leave it as is because it's important to someone and just copy-paste it? such tests are a maintenance nightmare
I have often heard developers say that test code should be 'ugly', and
plain as possible.
yes, they should be plain and no, the should not be ugly. it's your code so refactor it. but refactor in a way to make them readable documentation. every single test should be a tiny part of the documentation on it's own. for everyone, not just for you. without the need of looking anything in other helper files or functions