I have a class that can load an image, close it or rename it (at least for now, there will be more operations with opened file in future).
I've implemented it with Facade pattern (I think so).
When I tried to write tests for it, I quickly noticed that I need to test it with different pre-conditions (for example, when image was loaded before calling some method). And the amount of tests are huge and will grow very quickly when I add new operations. As I understand these are not unit tests, but rather are end-to-end tests.
I am new to TDD, so can anyone tell me is it normal practice to have such complicated tests?
I'm expecting answers about too much responsibility of my class, but lets think about alternatives:
// pseudo-code
// #1 - my way
function onRenameButtonClick(newName) {
imageFacade.rename(newName)
}
// #2 - the other way
function onRenameButtonClick(newName) {
if (imageController.isOpened()) {
imageRenamer.rename(imageController.image, newName)
}
}
In the end I still need to test correct behavior for #2, and it still will involve using different pre-conditions.
How do you deal with such cases? Is it normal or I'm doing something wrong?
P. S. here is a skeleton of my facade class, note that in config
there are pure functions, that do actual work, and actions
are stitching these pure functions and trigger events depending on the state.
function makeImageWTF(loader, renamer) {
return {
state: {
image: null,
filePath: null,
isLoading: false
},
config: {
loader,
renamer
},
triggers: {
opening: new Bus(),
opened: new Bus(),
closed: new Bus(),
renamed: new Bus(),
error: new Bus()
},
actions: {
open: function(path) {},
close: function() {},
rename: function(newName) {}
}
}
}
And here is a skeleton of tests
describe('ImageWTF', function() {
describe('initial state', function() {
it('should be that', function() {
var wtf = makeImageWTF()
assert.deepEqual({
image: null,
filePath: null,
isLoading: false,
}, wtf.state)
})
})
describe('#open(path)', function() {
it('sets isLoading')
it('calls config.loader with path')
it('sets image, fileName')
it('triggers loading, loaded or error')
it('clears isLoading')
})
describe('#close()', function() {
describe('when image is opened', function() {
it('resets image')
it('triggers closed')
})
describe('when image is NOT opened', function() {
it('triggers error')
})
})
describe('#rename()', function() {
describe('when image is opened', function() {
it('calls config.renamer with oldName and newName')
it('sets fileName')
it('triggers renamed')
})
describe('when image is NOT opened', function() {
it('triggers error')
})
})
})
(Unit) Test are just like production code - they can get complicated. But the goal, of course, should be to keep them as simple as possible.
If you do not have test for your code yet I suggest you start writing test to cover the most important use cases. Once you have those working in place you can refactor them. But for me the main focus would be to get test in place, you will learn a lot going down that path.
I would not care to much if they are not "unit test" from the start, change them to fit your application.
Try not to couple the tests to hard with the production code, since you want the flexibility to refactor the production code without changing the tests at the same time (and the other way around, of course). Unit test for me is really about making it easy and fast to change and refactor code. They will not catch all bugs or behavior in you application - for that you need to focus on other kinds of tests as well.