Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vaadin.browserless;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

/**
* Base JUnit 6 class for browserless tests.
*
* The class automatically scans classpath for routes and error views.
* Subclasses should typically restrict classpath scanning to a specific
* packages for faster bootstrap, by using {@link ViewPackages} annotation. If
* the annotation is not present a full classpath scan is performed
*
* <pre>
* {@code
* &#64;ViewPackages(classes = {CartView.class, CheckoutView.class})
* class CartViewTest extends BrowserlessTest {
* }
*
* &#64;ViewPackages(packages = {"com.example.shop.cart", "com.example.security"})
* class CartViewTest extends BrowserlessTest {
* }
*
* &#64;ViewPackages(
* classes = {CartView.class, CheckoutView.class},
* packages = {"com.example.security"}
* )
* class CartViewTest extends BrowserlessTest {
* }
* </pre>
*
* Set up of Vaadin environment is performed before each test by {@link
* #initVaadinEnvironment()} method, and will be executed before
* {@code @BeforeEach} methods defined in subclasses. At the same way, cleanup
* tasks operated by {@link #cleanVaadinEnvironment()} are executed after each
* test, and after all {@code @AfterEach} annotated methods in subclasses.
*
* Usually, it is not necessary to override {@link #initVaadinEnvironment()} or
* {@link #cleanVaadinEnvironment()} methods, but if this is done it is
* mandatory to add the {@code @BeforeEach} and {@code @AfterEach} annotations
* in the subclass, in order to have hooks handled by testing framework.
*
* A use case for overriding {@link #initVaadinEnvironment()} is to provide
* custom Flow service implementations supported by {@link
* com.vaadin.flow.di.Lookup} SPI. Implementations can be provided overriding
* {@link #initVaadinEnvironment()} and passing to super implementation the
* service classes that should be initialized during setup.
*
* <pre>
* {@code
* &#64;BeforeEach
* &#64;Override
* void initVaadinEnvironment() {
* super.initVaadinEnvironment(CustomInstantiatorFactory.class);
* }
* }
* </pre>
* <p/>
* To get a graphical ascii representation of the UI tree on failure add the
* annotation {@code @ExtendWith(TreeOnFailureExtension.class)} to the test
* class.
*
* @see ViewPackages
*/
public abstract class BrowserlessTest extends BaseBrowserlessTest
implements TesterWrappers {

@BeforeEach
protected void initVaadinEnvironment() {
super.initVaadinEnvironment();
}

@AfterEach
@Override
protected void cleanVaadinEnvironment() {
super.cleanVaadinEnvironment();
}

@Override
protected final String testingEngine() {
return "JUnit 6";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vaadin.browserless;

import java.util.Set;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.vaadin.browserless.internal.MockVaadin;
import com.vaadin.browserless.mocks.MockSpringServlet;
import com.vaadin.browserless.mocks.MockedUI;
import com.vaadin.browserless.mocks.SpringSecurityRequestCustomizer;

/**
* Base JUnit 6 class for browserless testing of applications based on Spring
* Framework.
*
* This class provides functionality of the Spring TestContext Framework, in
* addition to set up a mock Vaadin Spring environment, so that views and
* components built upon dependency injection and AOP can be correctly be
* handled during unit testing.
*
* Usually when unit testing a UI view it is not needed to bootstrap the whole
* application. Subclasses can therefore be annotated
* with @{@link org.springframework.test.context.ContextConfiguration} or other
* Spring Testing annotations to load only required component or to provide mock
* services implementations.
*
* <pre>
* {@code
* &#64;ContextConfiguration(classes = ViewTestConfig.class)
* class ViewTest extends SpringBrowserlessTest {
*
* }
* &#64;Configuration
* class ViewTestConfig {
* &#64;Bean
* MyService myService() {
* return new my MockMyService();
* }
* }
* }
* </pre>
*/
@ExtendWith({ SpringExtension.class })
@TestExecutionListeners(listeners = BrowserlessTestSpringLookupInitializer.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public abstract class SpringBrowserlessTest extends BrowserlessTest {

@Autowired
private ApplicationContext applicationContext;

@Override
protected Set<Class<?>> lookupServices() {
return Set.of(BrowserlessTestSpringLookupInitializer.class,
SpringSecurityRequestCustomizer.class);
}

@BeforeEach
protected void initVaadinEnvironment() {
scanTesters();
MockSpringServlet servlet = new MockSpringServlet(discoverRoutes(),
applicationContext, MockedUI::new);
MockVaadin.setup(MockedUI::new, servlet, lookupServices());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vaadin.browserless;

import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import com.vaadin.browserless.internal.PrettyPrintTree;
import com.vaadin.flow.component.UI;

/**
* JUnit 6 extension that will collect and output the component tree for the
* failing test UI.
* <p>
* This can help with identifying a problem that has happened in the test where
* a component is missing or has faulty data.
*/
public class TreeOnFailureExtension implements AfterTestExecutionCallback {

@Override
public void afterTestExecution(ExtensionContext extensionContext) {
boolean testFailed = extensionContext.getExecutionException()
.isPresent();
if (testFailed) {
final String prettyPrintTree = PrettyPrintTree.Companion
.ofVaadin(UI.getCurrent()).print();
extensionContext.publishReportEntry("Test "
+ extensionContext.getTestClass().get().getSimpleName()
+ "::" + extensionContext.getTestMethod().get().getName()
+ " failed with the tree:\n" + prettyPrintTree);
}
}
}
33 changes: 33 additions & 0 deletions browserless-test/junit6/src/test/java/com/example/SingleParam.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.Route;

@Route("param")
@Tag("div")
public class SingleParam extends Component implements HasUrlParameter<String> {
public String parameter;

@Override
public void setParameter(BeforeEvent event, String parameter) {
this.parameter = parameter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.Route;

@Route("template/:param")
@Tag("div")
public class TemplatedParam extends Component implements BeforeEnterObserver {
public String parameter;

@Override
public void beforeEnter(BeforeEnterEvent event) {
parameter = event.getRouteParameters().get("param").get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.base;

import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.ErrorParameter;
import com.vaadin.flow.router.HasErrorParameter;
import com.vaadin.flow.router.NotFoundException;

public class ErrorView extends VerticalLayout
implements HasErrorParameter<Exception> {
@Override
public int setErrorParameter(BeforeEnterEvent event,
ErrorParameter<Exception> parameter) {
if (parameter.getException() instanceof NotFoundException) {
throw (NotFoundException) parameter.getException();
}
throw new RuntimeException(parameter.getCaughtException());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.base;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;

@Route("helloworld")
public class HelloWorldView extends VerticalLayout {
public HelloWorldView() {
add(new Button("Hello, World!"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.base;

import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.QueryParameters;
import com.vaadin.flow.router.Route;

@Route("params")
public class ParametrizedView extends VerticalLayout
implements HasUrlParameter<Integer> {

Integer parameter;
QueryParameters qp;

@Override
public void setParameter(BeforeEvent event, Integer parameter) {
this.parameter = parameter;
qp = event.getLocation().getQueryParameters();
}
}
Loading
Loading