simulate key press in JUnit tests

2019-07-16 09:29发布

问题:

I am completely stuck in a java test; it's about sending by the test method the character 'a' to the JTextField of a JFrame component.

The JFrame class implements the KeyListener interface, and as such overrides KeyPressed, KeyTyped, and KeyReleased. Along with this, I transfer all the keypressed of the JTextField to the JFrame; inside the JFrame constructor I have :

JTextField txf_version = new JTextField();
txf_version.addKeyListener(this);

I would like to test this behavior and then to simulate the action of type a character in the JTextField.

all my attempts failed; I tried with the java.awt.Robot class, like this : hava a look at this other post in stack overflow, but I get a strange behavior : calling

robot.keyPress(KeyEvent.VK_A);

displays the character in my IDE directly, not in the virtual JFrame! try to play with requestFocus() or requestFocusInWindow() is ineffective.

I also tried with KeyEvents:

KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_PRESSED, System
        .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
bookWindow.txf_version.dispatchEvent(key);

but again the textfield's text property is not changed...

here is the method I have for now:

@Test
void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
      InterruptedException, NoSuchFieldException, IllegalAccessException {
  initTest();

  KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_PRESSED, System
        .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
  bookWindow.txf_version.dispatchEvent(key);

  System.out.println("dans txf_version : " + bookWindow.txf_version.getText
        ());

  assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());


}

I can have a look at the actual behavior by writing a main() method in the JFrame's child class, but I think it is useful to know how to simulate keys for swing components testing.

thank you

EDIT: I changed the code of my test according to AJNeufeld's answer, but it still doesn't work. Here is my test code :

@Test
void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
      InterruptedException, NoSuchFieldException, IllegalAccessException,
      InvocationTargetException {
//bookEditor2 & bookWindow
SwingUtilities.invokeAndWait(() -> {
  bookWindow = new BookWindow();
  VectorPerso two = new VectorPerso();
  two.add(le_livre_de_la_jungle);
  two.add(elogeMaths);
  bookWindow.setTableDatas(two);
  bookWindow.table.setRowSelectionInterval(1, 1);
  bookWindow.txf_version.requestFocusInWindow();
  KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_TYPED, System
          .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
  bookWindow.txf_version.dispatchEvent(key);
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  System.out.println("dans txf_version : " + bookWindow.txf_version.getText
          ());

  assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());
});


}

the plintln line produces a text in the console : "dans txf_version : 0", which indicates the key isn't send to the txf_version.

EDIT 2:

new try:

@Test
  void testBtnSaveChangesBecomesRedWhenVersionChanged() throws AWTException,
          InterruptedException, NoSuchFieldException, IllegalAccessException,
          InvocationTargetException {
    //bookEditor2 & bookWindow
    SwingUtilities.invokeAndWait(() -> {
      bookWindow = new BookWindow();
      VectorPerso two = new VectorPerso();
      two.add(le_livre_de_la_jungle);
      two.add(elogeMaths);
      bookWindow.setTableDatas(two);
      bookWindow.table.setRowSelectionInterval(1, 1);
      bookWindow.txf_version.requestFocusInWindow();
      KeyEvent key = new KeyEvent(bookWindow.txf_version, KeyEvent.KEY_TYPED, System

              .currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'a');
      bookWindow.txf_version.dispatchEvent(key);
    });
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    SwingUtilities.invokeAndWait(() -> {
      System.out.println("dans txf_version : " + bookWindow.txf_version.getText
              ());

      assertEquals(Color.RED, bookWindow.getBtnSaveChangesForegroundColor());
    });


  }

回答1:

I think you're doing a couple of things wrong, but without a complete example, it is hard to tell.

First, the JTextField is not really concerned with KEY_PRESSED events. It is concerned with the KEY_TYPED events.

Second, Swing processes events on the Event Dispatching Thread (EDT), which is not necessarily the thread that JUnit is going to be running on. You really shouldn't be changing things when you're not on the EDT. I'm not certain eventDispatch() does the switch to the EDT or not. It might. But it might also do it using invokeLater(), in which case the execution immediately passes to the assertEquals(), which fails, because the event processing hasn't happened yet.

Here is minimal, complete, verifiable example, which shows a keypress sent, which changes the button colour, and a JUnit test case which checks it and passes:

First, the code under test:

public class SwingUnitTesting extends JFrame {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(SwingUnitTesting::new);
    }

    JTextField tf = new JTextField();
    JButton btn = new JButton("Test Button");

    SwingUnitTesting() {
        add(tf, BorderLayout.PAGE_END);
        add(btn, BorderLayout.PAGE_START);

        tf.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                btn.setForeground(Color.RED);
            }
        });

        setSize(200, 80);
        setLocationByPlatform(true);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setVisible(true);
    }
}

And the unit test:

public class SwingUnitTestingTest {

    SwingUnitTesting sut;

    @Before
    public void setUp() throws Exception {
        SwingUtilities.invokeAndWait(() -> {
            sut = new SwingUnitTesting();
        });
    }

    @Test
    public void btnNotRedBeforeKeypress() throws InvocationTargetException, InterruptedException {
        SwingUtilities.invokeAndWait(() -> {
            assertNotEquals(Color.RED, sut.btn.getForeground());
        });
    }

    @Test
    public void btnRedAfterKeypress() throws InvocationTargetException, InterruptedException {
        SwingUtilities.invokeAndWait(() -> {
            sut.tf.requestFocusInWindow();
            sut.tf.dispatchEvent(new KeyEvent(sut.tf,
                    KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0,
                    KeyEvent.VK_UNDEFINED, 'A'));
            assertEquals(Color.RED, sut.btn.getForeground());
        });
    }
}

You can probably use some JUnit @Rule trickery or a custom runner to automatically change to the EDT when running swing tests.


Update:

I got curious, and tried to find an existing @Rule which puts the @Before, @Test, and @After code on to the EDT, but my Google-fu failed me; I know I've seen it before, but I couldn't find it. In the end, I created my own:

public class EDTRule implements TestRule {

    @Override
    public Statement apply(Statement stmt, Description dscr) {
        return new Statement() {
            private Throwable ex;

            @Override
            public void evaluate() throws Throwable {
                SwingUtilities.invokeAndWait(() -> {
                    try {
                        stmt.evaluate();
                    } catch (Throwable t) {
                        ex = t;
                    }
                });
                if (ex != null) {
                    throw ex;
                }
            }
        };
    }
}

Using this rule, the JUnit test becomes a little simpler:

public class SwingUnitTestingTest {

    @Rule
    public TestRule edt = new EDTRule();

    SwingUnitTesting sut;

    @Before
    public void setUp() throws Exception {
        sut = new SwingUnitTesting();
    }

    @Test
    public void btnNotRedBeforeKeypress() {
        assertNotEquals(Color.RED, sut.btn.getForeground());
    }

    @Test
    public void btnRedAfterKeypress() {
        sut.tf.requestFocusInWindow();
        sut.tf.dispatchEvent(
                new KeyEvent(sut.tf, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A'));
        assertEquals(Color.RED, sut.btn.getForeground());
    }
}

Tested on MacOS, with jdk1.8.0_121



回答2:

Yo, I guess you have two choices

1) It's still to find a solution using SWING (but in this case, I have no experience and any idead how to help you).

2) It's to use Sikulli java framework for testing desktop app. You can add the dependency then

  • make screenshot of your UI elements, and put them into test data folder of your app Using sikuli + JUnit
  • write simple test where you set a path for your pic of button (for example)
  • and write action, move, click or what actually you need. in very simple that will be looks like

    Button button = new Button("test-folder/pic1.jpg");

    button.click();

After run, you will see that, your cursor was move on button, and click by it.

For more details, find examples in web about Sikulli + Java.



回答3:

recently I had to test a customized KeyAdapter and I created a customized JTextField to dispatch key events. A piece of the code I used is bellow:

public class ProcessKeyOnTextFieldTest {

    @Test
    public void pressKeyTest() throws Exception {
        JTextFieldWithTypedKeySupport textField = new JTextFieldWithTypedKeySupport();

        textField.pressKey('a');
        textField.pressKey('b');

        assertEquals("ab", textField.getText());
    }

    class JTextFieldWithTypedKeySupport extends JTextField {

        int timestamp;

        void pressKey(char key) throws InvocationTargetException, InterruptedException {
            SwingUtilities.invokeAndWait(() -> super.processKeyEvent(createEvent(key)));
        }

        KeyEvent createEvent(char keyChar) {
            return new KeyEvent(this, KeyEvent.KEY_TYPED, timestamp++, 0, KeyEvent.VK_UNDEFINED, keyChar);
        }
    }
}


标签: java swing junit