Suppose I want to test this simple JButton
extension
package app;
import solution.common.utils.MathConverter;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class JDropdownButton
extends JButton
implements ActionListener {
private JPopupMenu popupMenu;
public JDropdownButton() {
this(null, null);
}
public JDropdownButton(String text, Icon icon) {
super(text, icon);
super.addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
if (null == popupMenu || null == popupMenu.getSubElements()) {
return;
}
if (!popupMenu.isVisible()) {
Point p = getLocationOnScreen();
popupMenu.setLocation(MathConverter.doubleToInt(p.getX()),
MathConverter.doubleToInt(p.getY()) + getHeight());
popupMenu.setVisible(true);
}
}
public void setDropdownMenu(JPopupMenu menu) {
popupMenu = menu;
if (popupMenu != null) {
popupMenu.setInvoker(this);
}
}
}
At this point, I'm only interested in testing if clicking the button shows the popup. If I run this
package app;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.awt.event.ActionEvent;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class JDropdownButtonTest {
@Test
void actionPerformed_showsPopup() {
JDropdownButton dropdownButton = new JDropdownButton();
JPopupMenu popupMenu = new JPopupMenu();
dropdownButton.setDropdownMenu(popupMenu);
// JFrame frame = new JFrame();
// frame.add(dropdownButton);
// frame.setVisible(true);
assertFalse(popupMenu.isVisible());
dropdownButton.doClick();
assertTrue(popupMenu.isVisible());
}
}
I get
java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location
at java.awt.Component.getLocationOnScreen_NoTreeLock(Component.java:2062)
at java.awt.Component.getLocationOnScreen(Component.java:2036)
at app.JDropdownButton.actionPerformed(JDropdownButton.java:50)
So it has to be displayed. Since Mockito can't tell a real call from a setup call, this will throw the same exception
@Test
void actionPerformed_showsPopup() {
JDropdownButton dropdownButton = spy(JDropdownButton.class);
given(dropdownButton.getLocationOnScreen()).willReturn(new Point(0,0));
JPopupMenu popupMenu = new JPopupMenu();
dropdownButton.setDropdownMenu(popupMenu);
// JFrame frame = new JFrame();
// frame.add(dropdownButton);
// frame.setVisible(true);
assertFalse(popupMenu.isVisible());
dropdownButton.doClick();
assertTrue(popupMenu.isVisible());
}
I can uncomment those lines, the test's run fine, but then the CI build on the TeamCity server would fail with a "headless exception" as no graphical environment would be available
So I need a robust GUI test that could be run in a headless environment too
How do I do that? Is updating the CI server's Docker image the only solution?
Not really an answer, more like a workaround. The doReturn()
structure avoids an actual method call (I guess because when()
returns a mock that "swallows" subsequent calls)
@Test
void actionPerformed_showsPopup() {
JDropdownButton dropdownButton = spy(JDropdownButton.class);
doReturn(new Point(0,0)).when(dropdownButton).getLocationOnScreen();
JPopupMenu popupMenu = new JPopupMenu();
dropdownButton.setDropdownMenu(popupMenu);
assertFalse(popupMenu.isVisible());
dropdownButton.doClick();
assertTrue(popupMenu.isVisible());
}