When To Share or not to Share Helper Methods in Tests

64 views
Skip to first unread message

Dave Schinkel

unread,
Oct 13, 2015, 5:49:13 PM10/13/15
to Clean Code Discussion
There are certain anti-patterns in unit testing or things that you shouldn't do because they introduce dependencies between tests.

I think I recall a while back, that it's bad to share helpers in some cases.

For our API, I've created a testUtil.js class that allows all our tests to create entity instances.  I had 2 options:

1) create these helpers at the bottom of each test module (eac *-test.js or if you're coding an OO language a test class)
2) for helpers which I felt are generic enough, create centralized test helpers that can be shared among test suites

is #2 bad?

Here's an example of my real testUtil.js module that a lot of our tests are calling when creating entities within the tests:

something feels inherently bad about this. Maybe it's not and it's just me but I wanted to throw this out there.  Should I change this up?

I am using these for both the real gateway and my in-memory gateway logic...both need to create entities which are returned back up to the use case modules (classes).

testUtilities.js

'use strict';

var countryEntity = require('../src/entities/country'),
stateEntity = require('../src/entities/state'),
cityEntity = require('../src/entities/city'),
personEntity = require('../src/entities/person'),
obituaryEntity = require('../src/entities/obituary'),
locationEntity = require('../src/entities/location'),
externalLinkEntity = require('../src/entities/externalLink'),
portraitImage = require('../src/entities/portraitImage'),
countryRequestModel = require('../src/models/http/request/countryRequest'),
countryResponseModel = require('../src/models/http/response/countryResponse'),
stateRequestModel = require('../src/models/http/request/stateRequest'),
stateResponseModel = require('../src/models/http/response/stateResponse'),
personRequestModel = require('../src/models/http/request/personRequest'),
lastNameEntity = require('../src/entities/lastName'),
lastNameRequestModel = require('../src/models/http/request/lastNameRequest'),
cityRequestModel = require('../src/models/http/request/cityRequest'),
cityResponseModel = require('../src/models/http/response/cityResponse');

module.exports = {
"clone": clone,
"createCountryEntity": createCountryEntity,
"createCountryRequestModel": createCountryRequestModel,
"createCountryResponseModel": createCountryResponseModel,
"createStateEntity": createStateEntity,
"createStateRequestModel": createStateRequestModel,
"createStateResponseModel": createStateResponseModel,
"createCityEntity": createCityEntity,
"createPersonEntity": createPersonEntity,
"createPersonRequestModel": createPersonRequestModel,
"createObituaryEntity": createObituaryEntity,
"createCityRequestModel": createCityRequestModel,
"createCityResponseModel": createCityResponseModel,
"createExternalLinkEntity": createExternalLinkEntity,
"createPortraitImage": createPortraitImage,
"createLocation": createLocation,
"addPerson": addPerson,
"createLastNameEntity": createLastNameEntity,
"createLastNameRequestModel": createLastNameRequestModel
};

function createCountryEntity(id, fullname, abbreviatedName, urlFriendlyName, states)
{
var country = clone(countryEntity);

country.id = id;
country.name.full = fullname;
country.name.abbreviated = abbreviatedName;
country.name.urlFriendlyName = urlFriendlyName;
country.states = states;

return country;
};

function createCountryRequestModel(id, fullname, abbreviatedName, urlFriendlyName)
{
var countryRequest = clone(countryRequestModel);

countryRequest.id = id;
countryRequest.name.full = fullname;
countryRequest.name.abbreviated = abbreviatedName;
countryRequest.name.urlFriendlyName = urlFriendlyName;

return countryRequest;
}

function createCountryResponseModel(countries){
var countryResponse = clone(countryResponseModel);

countryResponse.countries = countries;

return countryResponse;
}


function createStateEntity(stateId, countryId, fullname, abbreviatedName, urlFriendlyName, countries, cities)
{
var state = clone(stateEntity);

state.id = stateId;
state.name.full = fullname;
state.name.abbreviated = abbreviatedName;
state.name.urlFriendlyName = urlFriendlyName;
state.links.self = "/states/" + stateId;
state.links.parent = "/countries/" + countryId;
state.countries = countries;
state.cities = cities;

return state;
}

function createStateRequestModel(countryId, stateId, stateUrlFriendlyName, countries, countryUrlFriendlyName)
{
var stateRequest = clone(stateRequestModel);

stateRequest.countryId = countryId;
stateRequest.stateId = stateId;
stateRequest.name.urlFriendlyName = stateUrlFriendlyName;
stateRequest.countries = countries;
stateRequest.countryUrlFriendlyName = countryUrlFriendlyName;

return stateRequest;
}

function createStateResponseModel(states)
{
var stateResponse = clone(stateResponseModel);

stateResponse.states = states;

return stateResponse;
}

function createCityEntity(cityId, stateId, fullname, abbreviatedName, urlFriendlyName){
var city = clone(cityEntity);

city.id = cityId;
city.name.full = fullname;
city.name.abbreviated = abbreviatedName;
city.name.urlFriendlyName = urlFriendlyName;
city.links.self = "/cities/" + cityId;
city.links.parent = "/states/" + stateId;

return city;
}

function createCityRequestModel(countryId, stateId, cityId, urlFriendlyName, countryUrlFriendlyName, stateUrlFriendlyName)
{
var cityRequest = clone(cityRequestModel);

cityRequest.countryId = countryId;
cityRequest.stateId = stateId;
cityRequest.cityId = cityId;
cityRequest.name.urlFriendlyName = urlFriendlyName;
cityRequest.countryUrlFriendlyName = countryUrlFriendlyName;
cityRequest.stateUrlFriendlyName = stateUrlFriendlyName;

return cityRequest;
}

and the method list goes on and on...

Jakob Holderbaum

unread,
Oct 14, 2015, 2:19:26 AM10/14/15
to clean-code...@googlegroups.com
Hi Dave,

why do you think shared code between tests is bad by definition?

I maintain and treat test code like production code. And building some
generalized factory methods for entity creation is a good thing IMO.

I always go for a support/ folder in my test/ folder. There I collect
the test doubles and factories.

In your case, I would probably split up the factories by entity. Unless
you use all of them in all of your tests (I hope not ;) )

The Request/Response models are something that is debatable. Why would
you need a globally available factory for them? Aren't you using them
just in the context of the use case that is tested?

Cheers
Jakob

--
Jakob Holderbaum, M.Sc.
Consulting Software Engineer

0049 176 637 297 71
http://jakob.io/
http://jakob.io/mentoring/
ja...@holderbaum.io
@hldrbm

Dave Schinkel

unread,
Oct 20, 2015, 12:50:05 AM10/20/15
to Clean Code Discussion, mail...@jakob.io
why do you think shared code between tests is bad by definition? 

A while back, I had a chance to pair with a couple 8thlighters.  Maybe Eric can comment on this but I recall there were situations where it was good practice not to share code between tests because you make them I think fragile if you do so.  I don't remember if that's what exactly it was but I remember if in your test suites if you have certain dependencies setup, it can lead to fragile test suites.

The Request/Response models are something that is debatable. Why would 
you need a globally available factory for them? Aren't you using them 
just in the context of the use case that is tested? 

Yes, my controllers get the response model from my use cases.  This is a REST API so when I split up use cases into smaller files, it's nice that they share the same helpers to create those response models instead of duplicating that entity creation in all those separate files.  One of my use cases is getting extremely long now with tests.  I should probably start splitting them out.  And when I do, they're all going to be creating lets say a PersonResponseModel for my person tests for example.
Reply all
Reply to author
Forward
0 new messages