> Markdown version of [Spring-based Projects](https://vaadin.com/docs/v24/flow/testing/ui-unit/spring). Section index: [llms.txt](https://vaadin.com/docs/v24/flow/llms.txt)

# UI Unit Testing in Spring-based Projects

In Spring-based projects, views may use dependency injection to get references to service and other software components. To instantiate such views and handle navigation correctly, Vaadin needs special implementations of internal components, such as `SpringInstantiator`. Testing with `UIUnitTest` provides specialized base test classes that integrate with the Spring Testing Framework: `SpringUIUnitTest` for JUnit 5, and `SpringUIUnit4Test` for JUnit 4.

Subclasses can therefore rely on all of the features offered by the Spring Testing Framework. The test class can be annotated with `@ContextConfiguration` to give a reduced `ApplicationContext` for a faster startup, or the `@SpringBootTest` annotation for more complex scenarios.

Spring Test Example

```java
@ContextConfiguration(classes = ViewTestConfig.class)
class ViewTest extends SpringUIUnitTest {
    @Test
    public void setText_clickButton_notificationIsShown() {
        final HelloWorldView helloView = navigate(HelloWorldView.class);

        test(helloView.name).setValue("Test");
        test(helloView.sayHello).click();

        Notification notification = $(Notification.class).first();
        Assertions.assertEquals("Hello Test", test(notification).getText());
    }
}

@Configuration
class ViewTestConfig {

        @Bean
        GreetingService myService() {
                return new TestingGreetingService();
        }
}
```

Spring Boot Test Example

```java
@SpringBootTest
class ViewTest extends SpringUIUnitTest {
    @Test
    public void setText_clickButton_notificationIsShown() {
        final HelloWorldView helloView = navigate(HelloWorldView.class);

        test(helloView.name).setValue("Test");
        test(helloView.sayHello).click();

        Notification notification = $(Notification.class).first();
        Assertions.assertEquals("Hello Test", test(notification).getText());
    }
}
```

## <a id="additional-setup"></a>Additional Setup

In addition to [the TestBench dependency](https://vaadin.com/docs/v24/flow/testing/ui-unit/getting-started.md), make sure you add the Spring Boot test starter dependency to your project. Add the following to your `pom.xml` file:

pom.xml

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
```

## <a id="set-up-view-access-control"></a>Set Up View Access Control

To apply view access control, Vaadin requires a `NavigationAccessControl` to be registered as a `BeforeEnterListener` for the UI. For `@SpringBootTest` annotated tests, the checker is created and configured automatically. However, when testing with a restricted `ApplicationContext`, you may want to perform the setup yourself in a `Configuration` class by providing a `VaadinServiceInitListener` that executes this step.

Set Up NavigationAccessControl for Plain Spring Project

```java
@Configuration
class TestViewSecurityConfig {

    @Bean
    VaadinServiceInitListener setupViewSecurityScenario() {
        SpringNavigationAccessControl accessControl = new SpringNavigationAccessControl();
        accessControl.setLoginView(LoginView.class);
        return event -> {
            event.getSource().addUIInitListener(uiEvent -> {
                uiEvent.getUI().addBeforeEnterListener(accessControl);
            });
        };
    }
}
```

If you’re using the Vaadin Spring Add-On, you can instead import the out-of-the-box `NavigationAccessControlInitializer`. It requires only that you define a `NavigationAccessControl` bean.

Set Up NavigationAccessControl with Vaadin Spring Add-On

```java
@Configuration
@Import({NavigationAccessControlInitializer.class})
class TestViewSecurityConfig {

    @Bean
    NavigationAccessControl navigationAccessControl() {
        return new SpringNavigationAccessControl();
    }
}
```

`NavigationAccessControl` has been introduce in Vaadin 24.3. In a project based on an earlier Vaadin version, view security can be configured in the same way, but using the `ViewAccessChecker` and `ViewAccessCheckerInitializer` components:

Set Up ViewAccessChecker for Plain Spring Project

```java
@Configuration
class TestViewSecurityConfig {

    @Bean
    VaadinServiceInitListener setupViewSecurityScenario() {
        SpringViewAccessChecker viewAccessChecker = new SpringViewAccessChecker(new AccessAnnotationChecker());
        viewAccessChecker.setLoginView(LoginView.class);
        return event -> {
            event.getSource().addUIInitListener(uiEvent -> {
                uiEvent.getUI().addBeforeEnterListener(viewAccessChecker);
            });
        };
    }
}
```

Set Up ViewAccessChecker with Vaadin Spring Add-On

```java
@Configuration
@Import({ViewAccessCheckerInitializer.class})
class TestViewSecurityConfig {

    @Bean
    ViewAccessChecker viewAccessChecker() {
        return new SpringViewAccessChecker(new AccessAnnotationChecker());
    }
}
```

## <a id="testing-with-spring-security"></a>Testing with Spring Security

Vaadin comes with built-in security helpers that enable the annotation-based view access control mechanism, which integrates well with Spring Security. When using `SpringUIUnitTest`, if Spring Security is present on the classpath, the mock environment is instructed to fetch authentication details from Spring `SecurityContextHolder`.

With this support, you can use Spring Security test annotations — such as `@WithMockUser`, `@WithAnonymousUser`, or `@WithUserDetails` — to simulate different authentication scenarios with test method granularity. More information is available on the [Spring Security documentation](https://docs.spring.io/spring-security/reference/servlet/test/method.html#test-method-withmockuser) site. Authentication details are available before creating the UI instance and navigating to the default route. This way redirects to the login view aren’t performed when simulating logged-in users. In the same way, custom redirect logic for authenticated users works as expected.

To use Spring Security test annotations, first make sure the dependency is added to the project.

```xml
<dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
</dependency>
```

Then extend `SpringUIUnitTest` and annotate test methods to set up an authentication scenario. For the simplest use cases, use `@WithMockUser` or `@WithAnonymousUser`, providing the username and roles that should be granted.

Tests with Mock Users

```java
@SpringBootTest
public class ViewSecurityTest extends SpringUIUnitTest {

    @Test
    @WithAnonymousUser
    void anonymousUser_protectedView_redirectToLogin() {
        navigate("protected", LoginView.class);
    }

    @Test
    @WithAnonymousUser
    void anonymousUser_publicView_signInLinkPresent() {
        // public view is default page
        Assertions.assertInstanceOf(PublicView.class, getCurrentView());

        Anchor anchor = $(Anchor.class).withText("Sign in").first();
        Assertions.assertTrue(
                test(anchor).isUsable(),
                "Sign in link should be available for anonymous user");
    }

    @Test
    @WithMockUser(username = "admin", roles = "ADMIN")
    void adminUser_adminView_viewShown() {
        navigate(AdminRoleView.class);

        Assertions.assertTrue(
                $(Avatar.class).first().isVisible(),
                "Avatar should be visible for logged users");
    }
}
```

When custom User objects or complex grant rules should be used, provide a custom `UserDetailsService` and annotate the test method with `@WithUserDetails`.

Tests with Mock UserDetailsService

```java
@ContextConfiguration(classes = SecurityTestConfig.class)
class SpringUnitSecurityTest extends SpringUIUnitTest {

    @Test
    @WithUserDetails("admin")
    void superuser_adminView_viewShown() {
        navigate(AdminRoleView.class);

        Assertions.assertTrue(
                $(Avatar.class).first().isVisible(),
                "Avatar should be visible for logged users");
    }

    @Test
    @WithUserDetails
    void user_adminView_accessDenied() {
        RouteNotFoundError errorView = navigate("admin-role",
                RouteNotFoundError.class);
        Assertions.assertTrue(
                errorView.getElement().getChild(0).getOuterHTML()
                        .contains("Reason: Access denied"),
                "Admin view should be accessible only by users with ADMIN role");
    }


}

@Configuration
class SecurityTestConfig {

    @Bean
    UserDetailsService mockUserDetailsService() {

        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username)
                    throws UsernameNotFoundException {
                if ("user".equals(username)) {
                    return new User(username, UUID.randomUUID().toString(),
                            List.of(
                                new SimpleGrantedAuthority("ROLE_DEV"),
                                new SimpleGrantedAuthority("ROLE_USER")
                        ));
                }
                if ("admin".equals(username)) {
                    return new User(username, UUID.randomUUID().toString(),
                            List.of(
                                new SimpleGrantedAuthority("ROLE_SUPERUSER"),
                                new SimpleGrantedAuthority("ROLE_ADMIN")
                        ));
                }
                throw new UsernameNotFoundException(
                        "User " + username + " not exists");
            }
        };
    }
}
```

`D68CAC9E-6131-45C9-84E6-6D1CA1E44E81`
