Docs

How the Browserless Environment Differs

What the browserless environment creates, in which order, and which application behavior therefore needs a different approach in a test.

A browserless test runs your application’s server-side code against a mocked Vaadin environment created inside the JUnit test. Views, components, and services behave as they do in a running application, but the environment is built and torn down around each test method rather than by a servlet container at startup.

That ordering is observable. Two application patterns depend on it, and in both the test fails in a way that looks like an application defect: a bean that can’t be resolved, and a view that doesn’t match the authenticated user.

Test Lifecycle

For a SpringBrowserlessTest, each test method runs through these steps:

  1. JUnit creates the test instance, and Spring injects its @Autowired fields. No Vaadin environment exists yet — there’s no VaadinService, no VaadinSession, and no UI.

  2. JUnit extension callbacks run. SpringExtension populates the Spring SecurityContextHolder from @WithMockUser, @WithAnonymousUser, or @WithUserDetails.

  3. The browserless environment is created: VaadinService, VaadinSession, and UI, followed by navigation to the root route. The authentication from the previous step is already in place, so view access control sees the simulated user.

  4. The test method body runs.

  5. The environment is torn down, and the session is closed.

BrowserlessTest and QuarkusBrowserlessTest follow the same sequence without the Spring injection step.

The consequence is that the Vaadin session exists only inside the test method. Anything that needs a session — including anything Vaadin resolves per session — has to be reached from there, not from a field of the test class.

Session-Scoped Beans

A @VaadinSessionScope or @SessionScope bean can’t be injected into a test class field. The field is injected in step 1, when no session exists, so the bean can’t be resolved:

Source code
Doesn’t Work
@SpringBootTest
class CartViewTest extends SpringBrowserlessTest {

    @Autowired
    private Cart cart; // No session exists when this field is injected.
}

Because this is an instance creation failure, it fails every test in the class, not only the ones that use the bean.

Inject an ObjectProvider instead, and ask it for the bean inside the test method, where the session is available:

Source code
Works
@SpringBootTest
class CartViewTest extends SpringBrowserlessTest {

    @Autowired
    private ObjectProvider<Cart> cartProvider;

    @Test
    void addItem_cartContainsItem() {
        CartView view = navigate(CartView.class);
        Cart cart = cartProvider.getObject();

        test(view.addButton).click();

        Assertions.assertEquals(1, cart.getItems().size());
    }
}

Annotating the Cart field with @Lazy works as well, and so does injecting ApplicationContext and calling getBean(Cart.class) from the test method. All three defer the lookup to the moment the bean is first used.

Views and other components aren’t affected: they’re instantiated during navigation, inside the test method, so their own session-scoped dependencies resolve normally.

Authentication Applied During a Test

Apply the authentication before the test method starts, with a plain @WithMockUser, @WithAnonymousUser, or @WithUserDetails on the test class or method. It’s then in place in step 2, before the environment is created, so the initial navigation already reflects the authenticated user and nothing further is needed:

Source code
Java
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminOpensAdminView_avatarShown() {
    AdminView view = navigate(AdminView.class);

    Assertions.assertTrue(find(Avatar.class).single().isVisible());
}

See Spring Security Testing for the full setup.

Some scenarios need the sign-in to happen during the test itself, such as a login form, or a view that has to be asserted both before and after the user signs in. Authentication established then — from the test method body, or with setupBefore = TestExecutionEvent.TEST_EXECUTION — arrives after the environment has navigated in step 3. Subsequent requests use the new authentication, but the view rendered during setup stays in place, so getCurrentView() still returns the result of that earlier, anonymous navigation.

Navigate again after signing in:

Source code
Java
@Test
@WithMockUser(username = "admin", roles = "ADMIN",
        setupBefore = TestExecutionEvent.TEST_EXECUTION)
void adminSignsInDuringTest_adminViewShown() {
    // Setup navigated to the root route while the user was still anonymous,
    // and access control redirected that navigation to the login view.
    Assertions.assertInstanceOf(LoginView.class, getCurrentView());

    // Navigating again applies access control to the current authentication.
    navigate(AdminView.class);

    Assertions.assertTrue(find(Avatar.class).single().isVisible());
}
Note
Reloading Isn’t Enough
Reloading with Page.reload() isn’t a substitute for navigating again. It recreates the UI and renders the location that is currently active, the same as pressing reload in a browser; when access control has redirected to the login view, that location is the login view.

Components That Don’t Exist Yet

Component queries walk the server-side component tree, which holds only what has been created. A component rendered per item exists once something renders it, and the contents of an overlay are attached only while the overlay is open. Until then, a query returns an empty result rather than an error. See Testing Overlay Components for how to reach them.

4B6C9E13-7A85-42D0-9F3B-1C8E5D4A2B76

Updated