mvysny / karibu-testing

Vaadin Server-Side Browserless Containerless Unit Testing
Apache License 2.0
111 stars 14 forks source link

Testing Upload component #152

Closed satorstefan closed 1 year ago

satorstefan commented 1 year ago

Hello Martin,

I wanted to test: public class Upload(package com.vaadin.flow.component.upload;)

but during test execution a null pointer exception is thrown: java.lang.NullPointerException at com.vaadin.flow.server.communication.StreamRequestHandler.generateURI(StreamRequestHandler.java:148) at com.vaadin.flow.server.StreamResourceRegistry.getURI(StreamResourceRegistry.java:144) at com.vaadin.flow.server.StreamResourceRegistry.getURI(StreamResourceRegistry.java:127) at com.vaadin.flow.internal.nodefeature.ElementAttributeMap.doSetResource(ElementAttributeMap.java:159) at com.vaadin.flow.internal.nodefeature.ElementAttributeMap.setResource(ElementAttributeMap.java:143) at com.vaadin.flow.dom.impl.BasicElementStateProvider.setAttribute(BasicElementStateProvider.java:332) at com.vaadin.flow.dom.Element.setAttribute(Element.java:341) at com.vaadin.flow.component.upload.Upload.(Upload.java:90) at com.vaadin.flow.component.upload.Upload.(Upload.java:127)

I thought every component can be tested in a unit test?

mvysny commented 1 year ago

Yup, Upload is also supported. Which Vaadin version are you using please?

satorstefan commented 1 year ago

I use: 23.2.2</vaadin.version>

satorstefan commented 1 year ago

Here a part of the test: @ExtendWith(MockitoExtension.class) class DocumentEditorTest {

DocumentEditor sut;

DocumentEditorPage page;

@Mock
DocumentPool pool;

@BeforeEach
void setUp() {
    sut = new DocumentEditor(pool, DocumentEditor.DocumentEditorMode.Create);
}

@Disabled
@Test
void saving_will_fire_event_with_all_data() {
    page = DocumentEditorPage.forViewTest(sut);

    String name = getRandomName();
    page.enterData(new DocumentEditorData(name));
satorstefan commented 1 year ago
public DocumentEditor(DocumentPool pool, DocumentEditorMode m) {
    this.pool = pool;
    this.mode = m;

    buffer = new MemoryBuffer();
    upload = new WorxafeUpload(buffer);
satorstefan commented 1 year ago

public class WorxafeUpload extends Upload { public WorxafeUpload(Receiver receiver) { super(receiver); setClassName("savebutton"); }

mvysny commented 1 year ago

The NPE is thrown at StreamRequestHandler.generateURI(StreamRequestHandler.java:148) which contains the following line: builder.append(UI.getCurrent().getUIId()).append(PATH_SEPARATOR);. The NPE can only be thrown because UI.getCurrent() is null, which means that you most probably forgot to call MockVaadin.setup().

satorstefan commented 1 year ago

Hello Martin,

is there any way to tell which vaadin components require to call MockVaadin.setup()?

mvysny commented 1 year ago

Not sure, never thought about that. I simply always call MockVaadin.setup().

satorstefan commented 1 year ago

I try to understand all possible ways, ordered by cost/execution time, and their benefits/drawbacks to test the vaadin/ui related classes.

In the order of quick to slow:

The simplest way to test something seems to instantiate the class directly:

1)

class SafeguardPoolEditorTest {

    SafeguardEditor sut;
    SafeguardEditorPage page;

    @BeforeEach
    void setUp() {
        sut = new SafeguardEditor(Collections.emptyList());
    }

    @Test
    void saveButtons_are_disabled_when_no_change() {
        page = SafeguardEditorPage.forViewTest(sut);

        sut.edit(new SafeguardUIModel(UUID.randomUUID()));

        page.saveIsEnabled();
    }
}

For some components that does not work. At least not for the upload component.

2) So the next steps seem to be to add MockVaadin.setup(). But does the component have to be part of a @Route already, Yes or no?

3) Then the component can be part of the application executed during a karibu/Junit test. That is what I am doing now.

4) Testing with a tool like selenium that controls the browser.

5) Testing the application by comparing expected/actual screenshots of the ui.

6) Have a Human tester test the application.

mvysny commented 1 year ago

If a component accesses UI.getCurrent() in its sources, then that's a sign that it requires MockVaadin.setup(). If a component doesn't access UI.getCurrent() then it doesn't require a call to MockVaadin.setup().

MockVaadin.setup() sets the current UI, Session etc, so that UI.getCurrent() returns a non-null value.

The component doesn't have to be a part of a route, in order to be tested.