> Markdown version of [Spring-based Projects](https://vaadin.com/docs/v23/testing/ui-unit/spring). Section index: [llms.txt](https://vaadin.com/docs/v23/testing/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 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 {

}

@Configuration
class ViewTestConfig {

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

Spring Boot test example

```java
@SpringBootTest
class ViewTest extends SpringUIUnitTest {

}
```

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

To apply view access control, Vaadin requires a `ViewAccessChecker` to be registered as a `BeforeEnterListener` for the UI. For `@SpringBootTest` annotated tests, the checker is automatically created and configured. 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 a ViewAccessChecker for a 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);
            });
        };
    }
}
```

If you are using the Vaadin Spring Add-On, you can instead import the out-of-the-box `ViewAccessCheckerInitializer`, which only requires you to define a `ViewAccessChecker` bean.

Set up a ViewAccessChecker with the 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, so 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`
