Reputation: 20745
I am trying write a unit test for a "save as" action using mockito. Part of that action is making and showing a FileDialog where the user can enter a file to save to. Choosing a file is not something I can automate, so I want to mock the FileDialog and mock mockedFileDialog.getFiles()
to return the file I want to "save" to.
The problem is that the file dialog is created in a parent class of my "save as" action. when(..)
seems to only work on mocks, but mocking the class I want to test defeats the point of the exercise. Then I am only testing if methods are called, which is not very useful. I could just run AbstractSaveAction.saveNet(..)
in the test, but this way I can not be sure that the save action is actually working - I would just be testing if there is a method that would save something, but I can't test if there is a chain of events that invokes this function.
The test below would yield:
SaveActionTest.java:[60,24] getFileDialog(java.lang.String,java.lang.String) has protected access in application.AbstractSaveAction
What is the correct way to test this action?
Code to be tested
public class SaveAsAction extends AbstractSaveAction {
public SaveAsAction(Controller controller, View view) {
super("", "", controller, view);
}
@Override
public void actionPerformed(ActionEvent e) {
saveAsNet();
}
}
public abstract class AbstractSaveAction extends GuiAction {
public AbstractSaveAction(..) {
//...
}
protected final File saveAsFile(FileDialog fileDialog, String extension) {
//...
}
protected final void saveAsNet() {
FileDialog fileDialog = getFileDialog("Save the world", "xml");
File file = saveAsFile(fileDialog, "xml");
saveNet(file);
}
protected final void saveNet(File file) {
try {
controller.saveAsFile(file);
} catch (TicklishPolarBearException e) {
//Panic
}
}
protected final FileDialog getFileDialog(String title, final String extension) {
FileDialog fileDialog = new FileDialog(view, title, FileDialog.SAVE);
fileDialog.setFile("*." + extension);
fileDialog.setFilenameFilter(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith("." + extension);
}
});
return fileDialog;
}
}
Test
@Test
public void performsSaveAsWhenNetHasNoFile()
throws AllKindsOfExceptions {
NetName normalName = new NormalNetName("");
when(mockNet.getName()).thenReturn(normalName);
File file = new File("test.xml");
//This will error out
when(saveAction.getFileDialog("Save the world", "xml")).thenReturn(mockFileDialog);
when(mockFileDialog.getFiles()).thenReturn(new File[]{file});
saveAsAction.actionPerformed(null);
verify(mockController).saveAsFile(file);
}
Upvotes: 1
Views: 1826
Reputation: 24498
Assuming you use the same package and (per comment) can remove the final
qualifier from methods, I would consider adding some protected
setter methods to that you can inject mocks into the AbstractSaveAction
. This is somewhat controversial, but it will work.
I have a working example, but the gist is the following in the Production code:
public abstract class AbstractSaveAction extends GuiAction {
// ...
// for tests only. Consider naming as 'testFileDialog' if you prefer
private FileDialog fileDialog = null;
// for tests only
protected void setFileDialog(FileDialog fileDialog) {
this.fileDialog = fileDialog;
}
// for tests only
protected void setController(Controller controller) {
this.controller = controller;
}
protected FileDialog buildFileDialog(String title) {
FileDialog result = null;
if (this.fileDialog != null) {
result = this.fileDialog;
} else {
result = new FileDialog(view, title, FileDialog.SAVE);
}
return result;
}
protected FileDialog getFileDialog(String title, final String extension) {
// This is new. Get a FileDialog, and if in test-mode
// so be it... It is a light-touch.
FileDialog fileDialog = buildFileDialog(title);
// ...
}
}
and then the test looks like:
@Test
public void performsSaveAsWhenNetHasNoFile() {
SaveAsAction saveAsAction = new SaveAsAction(null, null);
FileDialog mockFileDialog = mock(FileDialog.class);
Controller mockController = mock(Controller.class);
Net mockNet = mock(Net.class);
NetName normalName = new NormalNetName("");
when(mockNet.getName()).thenReturn(normalName);
File file = new File("test.xml");
when(mockFileDialog.getFiles()).thenReturn(new File[]{file});
// these steps are crucial
saveAsAction.setFileDialog(mockFileDialog);
saveAsAction.setController(mockController);
// test
saveAsAction.actionPerformed(null);
verify(mockController).saveAsFile(file);
}
Upvotes: 1