Nathan.30
(Nathan Lively)
January 16, 2025, 1:00am
1
I can’t seem to assert against things in MainLayout. It acts like AuthenticatedUser is not present.
Here’s the test. Everything passes until the final assertion.
@Test
void givenUserHasZeroCredit_whenNavigatingToAnyPage_thenCreditBalanceVisibleWithZero() {
saveAccountAndDeleteAll();
loginAndNavigate(KeywordCollectionsView.class);
assertThat(authenticatedUser.get()).isPresent();
@NotNull Paragraph creditBalanceParagraph = LocatorJ._get(Paragraph.class, s -> s.withId("credit-balance"));
assertThat(creditBalanceParagraph).isNotNull();
assertThat(creditBalanceParagraph.getText()).isEqualTo("Credit balance: 0");
}
Here’s the result:
org.opentest4j.AssertionFailedError:
expected: “Credit balance: 0”
but was: “Credit balance: ?”
Here’s the relevant production code:
Optional<Account> currentAccount = authenticatedUser.get();
String credit = "?";
if (currentAccount.isPresent()) {
credit = String.valueOf(currentAccount.get().appCredit().balance());
}
Paragraph creditBalanceParagraph = new Paragraph("Credit balance: " + credit);
Nathan.30
(Nathan Lively)
January 16, 2025, 1:00am
2
Full test class:
@Tag("ui")
class MainLayoutTest extends KaribuTest {
@Autowired
AuthenticatedUser authenticatedUser;
@Test
void givenUserHasZeroCredit_whenNavigatingToAnyPage_thenCreditBalanceVisibleWithZero() {
saveAccountAndDeleteAll();
loginAndNavigate(KeywordCollectionsView.class);
assertThat(authenticatedUser.get()).isPresent();
@NotNull Paragraph creditBalanceParagraph = LocatorJ._get(Paragraph.class, s -> s.withId("credit-balance"));
assertThat(creditBalanceParagraph).isNotNull();
assertThat(creditBalanceParagraph.getText()).isEqualTo("Credit balance: 0");
}
@Test
void givenUserHasCredit100_whenNavigatingToAnyPage_thenCreditBalanceVisibleWith100() {
accountRepository.deleteAllEverywhere();
Account account = new AccountBuilder().withAppCredit(100).build();
accountRepository.save(account);
loginAndNavigate("");
@NotNull Paragraph creditBalanceParagraph = LocatorJ._get(Paragraph.class, s -> s.withId("credit-balance"));
assertThat(creditBalanceParagraph).isNotNull();
assertThat(creditBalanceParagraph.getText()).isEqualTo("Credit balance: 100");
}
}
Nathan.30
(Nathan Lively)
January 16, 2025, 1:01am
3
And full MainLayout:
@JsModule("./prefers-color-scheme.js")
@Layout
@AnonymousAllowed
public class MainLayout extends AppLayout {
private H1 viewTitle;
private final AuthenticatedUser authenticatedUser;
private Registration broadcasterRegistration;
public MainLayout(AuthenticatedUser authenticatedUser) {
this.authenticatedUser = authenticatedUser;
setPrimarySection(Section.DRAWER);
addDrawerContent();
addHeaderContent();
}
private void addHeaderContent() {
DrawerToggle toggle = new DrawerToggle();
toggle.setAriaLabel("Menu toggle");
viewTitle = new H1();
viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
Optional<Account> currentAccount = authenticatedUser.get();
String credit = "?";
if (currentAccount.isPresent()) {
credit = String.valueOf(currentAccount.get().appCredit().balance());
}
Paragraph creditBalanceParagraph = new Paragraph("Credit balance: " + credit);
creditBalanceParagraph.setId("credit-balance");
creditBalanceParagraph.addClassNames(LumoUtility.FontSize.SMALL, LumoUtility.Margin.NONE);
HorizontalLayout headerLayout = new HorizontalLayout(viewTitle, creditBalanceParagraph);
headerLayout.setWidthFull();
headerLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
headerLayout.addClassNames(LumoUtility.Padding.Right.LARGE);
addToNavbar(true, toggle, headerLayout);
}
private void addDrawerContent() {
Span appName = new Span("B2B Demand Generation Strategy");
appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE);
Header header = new Header(appName);
header.addClickListener(_ -> header.getUI().ifPresent(ui -> ui.navigate(KeywordCollectionsView.class)));
Scroller scroller = new Scroller(createNavigation());
addToDrawer(header, scroller, createFooter());
}
private SideNav createNavigation() {
SideNav nav = new SideNav();
List<MenuEntry> menuEntries = MenuConfiguration.getMenuEntries();
menuEntries.forEach(entry -> {
if (entry.icon() != null) {
nav.addItem(new SideNavItem(entry.title(), entry.path(), new SvgIcon(entry.icon())));
} else {
nav.addItem(new SideNavItem(entry.title(), entry.path()));
}
});
return nav;
}
private Footer createFooter() {
Footer layout = new Footer();
Optional<Account> maybeUser = authenticatedUser.get();
if (maybeUser.isPresent()) {
Account account = maybeUser.get();
String name;
if (account.person() != null && account.person().name() != null) {
name = account.person().name();
} else {
name = account.username();
}
Avatar avatar = new Avatar(name);
avatar.setThemeName("xsmall");
avatar.getElement().setAttribute("tabindex", "-1");
MenuBar userMenu = new MenuBar();
userMenu.setThemeName("tertiary-inline contrast");
MenuItem userName = userMenu.addItem("");
Div div = new Div();
div.add(avatar);
div.add(name);
div.add(new Icon("lumo", "dropdown"));
div.getElement().getStyle().set("display", "flex");
div.getElement().getStyle().set("align-items", "center");
div.getElement().getStyle().set("gap", "var(--lumo-space-s)");
userName.add(div);
SubMenu subMenu = userName.getSubMenu();
subMenu.addItem("Sign out", _ -> authenticatedUser.logout());
layout.add(userMenu);
} else {
Anchor loginLink = new Anchor("login", "Sign in");
layout.add(loginLink);
}
return layout;
}
@Override
protected void afterNavigation() {
super.afterNavigation();
viewTitle.setText(getCurrentPageTitle());
}
private String getCurrentPageTitle() {
return MenuConfiguration.getPageHeader(getContent()).orElse("");
}
@Override
protected void onAttach(AttachEvent attachEvent) {
UI ui = attachEvent.getUI();
broadcasterRegistration = Broadcaster.register(message -> ui.access(() -> {
if (!"REFRESH_GRIDS".equals(message)) {
Notification notification = Notification.show(message);
notification.setPosition(Notification.Position.TOP_CENTER);
}
}));
}
@Override
protected void onDetach(DetachEvent detachEvent) {
if (broadcasterRegistration != null) {
broadcasterRegistration.remove();
broadcasterRegistration = null;
}
}
}
mavi1
(Martin Vyšný)
January 17, 2025, 6:42am
4
Yup, Spring Security needs a bit of wiring - this is the limitation of KaribuTesting since no Spring Security Filters/Servlets are triggered by default. Please see karibu-testing/karibu-testing-v10-spring at master · mvysny/karibu-testing · GitHub
1 Like
Nathan.30
(Nathan Lively)
January 18, 2025, 3:43pm
5
Thanks! I did try:
@BeforeEach
public void setupKaribu() {
MockVaadin.INSTANCE.setMockRequestFactory(session -> new FakeRequest(session) {
@Override
public Principal getUserPrincipal() {
return SecurityContextHolder.getContext().getAuthentication();
}
});
final Function0<UI> uiFactory = UI::new;
final SpringServlet servlet = new MockSpringServlet(routes, ctx, uiFactory);
MockVaadin.setup(uiFactory, servlet);
}
Same result.
I also tried just moving the tests to a normal view class test. Same result.
knoobie
(Christian Knoop)
January 19, 2025, 12:23pm
6
Why do you think it should change anything? Your tests aren’t annotated with any Spring Security Test annotation like “WithMockUser” - so even now that you are setting the correct context… nobody is filling that context.
1 Like
Nathan.30
(Nathan Lively)
January 20, 2025, 8:33pm
7
mavi1
(Martin Vyšný)
January 23, 2025, 8:15am
8
I agree with Christian: the code above simply populates the VaadinRequest with user grabbed from Spring Security; but if you’re not setting any user to Spring Security then it will simply be left unpopulated.
You should either configure your Spring tests to login with a user, or you should then create a fake Principal and return it from the FakeRequest.
Maybe this example app could help: GitHub - mvysny/vaadin-spring-karibu-testing
1 Like
Nathan.30
(Nathan Lively)
January 23, 2025, 2:27pm
9
Thanks Martin. I’ll take a look. So, just to be clear, you are saying that the test setup outline in the blog post that I linked to does not log the user in? I guess I thought that’s what the login method in KaribuTest was handling.
mavi1
(Martin Vyšný)
January 24, 2025, 6:49am
10
Yes, that’s true. I’ve updated the documentation to mention this, here’s a copy of the docs:
Note that this will only carry the currently logged-in user (Principal) from Spring Security over to the faked Vaadin environment - a job that’s usually done by Spring servlet filter. In this faked environment the filter is not triggered, and therefore this manual step is necessary. Note that the code above doesn’t log in the user: you’ll need to log in user in Spring Security beforehand, either via annotations or manually.
1 Like