Sumurai8
Sumurai8

Reputation: 20745

Mocking protected method of parent class yields "has protected access" error

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

Answers (1)

Michael Easter
Michael Easter

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

Related Questions