Skip to content

Commit

Permalink
Stop showing devtools reload msg SB >= 2.4 (#685) (#687)
Browse files Browse the repository at this point in the history
The issue has been fixed in Spring Boot 2.4.0.
Bumps the project version to Spring Boot 2.4.0.
Bumps Mockito to 3.6.0.
PowerMock is quite broken with never mockito versions and should be avoided.
It seems to be only used due to mocking private methods, which is questionable.
  • Loading branch information
Pekka Hyvönen authored Nov 20, 2020
1 parent 2cce981 commit f04b15f
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 46 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@

<!-- These are typically overridden with BOMs -->
<vaadin.flow.version>5.0-SNAPSHOT</vaadin.flow.version>
<spring-boot.version>2.2.0.RELEASE</spring-boot.version>
<spring-boot.version>2.4.0</spring-boot.version>

<!-- Additional manifest fields -->
<Vaadin-License-Title>Apache License 2.0</Vaadin-License-Title>

<junit.version>4.12</junit.version>
<maven.clean.plugin.version>3.0.0</maven.clean.plugin.version>
<mockito.version>3.1.0</mockito.version>
<powermock.version>2.0.7</powermock.version>
<mockito.version>3.6.0</mockito.version>
<powermock.version>2.0.9</powermock.version>
</properties>

<dependencyManagement>
Expand Down
2 changes: 1 addition & 1 deletion vaadin-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,35 @@
import com.vaadin.flow.router.ErrorParameter;
import com.vaadin.flow.router.NotFoundException;
import com.vaadin.flow.router.RouteNotFoundError;
import com.vaadin.flow.server.frontend.FrontendVersion;
import org.springframework.boot.SpringBootVersion;

public class SpringRouteNotFoundError extends RouteNotFoundError {

@Override
public int setErrorParameter(BeforeEnterEvent event,
ErrorParameter<NotFoundException> parameter) {
int retval = super.setErrorParameter(event, parameter);

if (!event.getUI().getSession().getConfiguration().isProductionMode()) {
// Alert user about potential issue with Spring Boot Devtools losing
// routes https://github.com/spring-projects/spring-boot/issues/19543
String customMessage = "<span>When using Spring Boot Devtools with "
+ "automatic reload, please note that routes can sometimes be "
+ "lost due to a <a href ='https://github.com/spring-projects/spring-boot/issues/19543'>"
+ "compilation race condition</a>. See "
+ "<a href='https://vaadin.com/docs/flow/workflow/setup-live-reload-springboot.html'>"
+ "the documentation</a> for further workarounds and other "
+ "live reload alternatives.";
getElement().appendChild(new Html(customMessage).getElement());
// This has been fixed since Spring Boot 2.4.0
String springBootVersion = SpringBootVersion.getVersion();
if (springBootVersion == null || springBootVersion.isEmpty()) {
return retval;
}
// the version is e.g. "2.2.0.RELEASE" or "2.4.0" ...
FrontendVersion version = new FrontendVersion(springBootVersion);
if (version.isOlderThan(new FrontendVersion(2, 4, 0))) {
String customMessage = "<span>When using Spring Boot Devtools with "
+ "automatic reload, please note that routes can sometimes be "
+ "lost due to a <a href ='https://github.com/spring-projects/spring-boot/issues/19543'>"
+ "compilation race condition</a>. See "
+ "<a href='https://vaadin.com/docs/flow/workflow/setup-live-reload-springboot.html'>"
+ "the documentation</a> for further workarounds and other "
+ "live reload alternatives.";
getElement().appendChild(new Html(customMessage).getElement());
}
}
return retval;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.vaadin.flow.spring;

import com.google.common.collect.Maps;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

import com.google.common.collect.Maps;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.router.BeforeEnterEvent;
Expand All @@ -14,29 +21,23 @@
import com.vaadin.flow.server.startup.DevModeInitializer;
import com.vaadin.flow.server.startup.ServletDeployer;
import com.vaadin.flow.spring.router.SpringRouteNotFoundError;

import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

@RunWith(PowerMockRunner.class)
@PrepareForTest({DevModeInitializer.class,
VaadinServletContextInitializer.SpringStubServletConfig.class,
Expand All @@ -60,7 +61,9 @@ public class VaadinServletContextInitializerTest {

@Before
public void init() {
PowerMockito.mockStatic(VaadinServletContextInitializer.SpringStubServletConfig.class);
MockitoAnnotations.openMocks(this);
PowerMockito.mockStatic(
VaadinServletContextInitializer.SpringStubServletConfig.class);
PowerMockito.mockStatic(ServletDeployer.class);
PowerMockito.mockStatic(ServletDeployer.StubServletConfig.class);
PowerMockito.mockStatic(DevModeInitializer.class);
Expand All @@ -77,13 +80,19 @@ public void onStartup_devModeNotInitialized_devModeInitialized() throws Exceptio
// Simulate Spring context start only
vaadinServletContextInitializer.onStartup(servletContext);

// This is how PowerMockito works, call PowerMockito.verifyStatic() first
// to start verifying behavior of DevModeInitializer static methods
PowerMockito.verifyStatic(DevModeInitializer.class);
// IMPORTANT: Call the static method we want to verify.
// In our case, we want to check if Dev Mode has been started within onStartup() call,
// that means DevModeInitializer.initDevModeHandler() should has been called exactly one time
DevModeInitializer.initDevModeHandler(Mockito.any(), Mockito.any(), Mockito.any());
try (MockedStatic<DevModeInitializer> theMock = Mockito
.mockStatic(DevModeInitializer.class)) {
// IMPORTANT: Call the static method we want to verify.
// In our case, we want to check if Dev Mode has been started within
// onStartup() call,
// that means DevModeInitializer.initDevModeHandler() should has
// been called exactly one time
DevModeInitializer.initDevModeHandler(Mockito.any(), Mockito.any(),
Mockito.any());
theMock.verify(() -> DevModeInitializer.initDevModeHandler(
Mockito.any(), Mockito.any(), Mockito.any()));
theMock.verifyNoMoreInteractions();
}
}

@Test
Expand All @@ -99,15 +108,19 @@ public void onStartup_devModeAlreadyInitialized_devModeInitializationSkipped() t
devModeInitializer.process(Collections.emptySet(), servletContext);
vaadinServletContextInitializer.onStartup(servletContext);

// This is how PowerMockito works, call PowerMockito.verifyStatic() first
// to start verifying behavior of DevModeInitializer static methods
PowerMockito.verifyStatic(DevModeInitializer.class);
// IMPORTANT: Call the static method we want to verify.
// In our case, we want to check if Dev Mode has been started within
// devModeInitializer.process() call (i.e. from Servlet Container), and not started again
// within DevModeInitializer.initDevModeHandler() (Spring context),
// so, we expect this method has been called exactly one time:
DevModeInitializer.initDevModeHandler(Mockito.any(), Mockito.any(), Mockito.any());
try (MockedStatic<DevModeInitializer> theMock = Mockito
.mockStatic(DevModeInitializer.class)) {
// IMPORTANT: Call the static method we want to verify.
// In our case, we want to check if Dev Mode has been started within
// onStartup() call,
// that means DevModeInitializer.initDevModeHandler() should has
// been called exactly one time
DevModeInitializer.initDevModeHandler(Mockito.any(), Mockito.any(),
Mockito.any());
theMock.verify(() -> DevModeInitializer.initDevModeHandler(
Mockito.any(), Mockito.any(), Mockito.any()));
theMock.verifyNoMoreInteractions();
}
}

@Test
Expand Down Expand Up @@ -209,7 +222,9 @@ private Runnable initRouteNotFoundMocksAndGetContextInitializedMockCall(
Mockito.when(applicationContext.getBeanNamesForType(VaadinScanPackagesRegistrar.VaadinScanPackages.class))
.thenReturn(new String[]{});
PowerMockito.when(AutoConfigurationPackages.class, "has",
applicationContext).thenReturn(false);
applicationContext)
// https://github.com/powermock/powermock/issues/992
.thenAnswer((Answer<Boolean>) invocation -> false);

ServletContextEvent initEventMock = Mockito.mock(ServletContextEvent.class);
Mockito.when(initEventMock.getServletContext()).thenReturn(servletContext);
Expand All @@ -224,7 +239,9 @@ private DevModeInitializer getStubbedDevModeInitializer() throws Exception {
"createDeploymentConfiguration",
Mockito.any(),
Mockito.any())
.thenReturn(deploymentConfiguration);
// https://github.com/powermock/powermock/issues/992
.thenAnswer(
(Answer<DeploymentConfiguration>) invocation -> deploymentConfiguration);

PowerMockito.when(DevModeInitializer.class,
"isDevModeAlreadyStarted",
Expand All @@ -242,8 +259,9 @@ private VaadinServletContextInitializer getStubbedVaadinServletContextInitialize
"createDeploymentConfiguration",
Mockito.any(),
Mockito.any(),
Mockito.any())
.thenReturn(deploymentConfiguration);
// https://github.com/powermock/powermock/issues/992
Mockito.any()).thenAnswer(
(Answer<DeploymentConfiguration>) invocation -> deploymentConfiguration);

PowerMockito.doReturn(Stream.empty())
.when(vaadinServletContextInitializerMock,
Expand All @@ -263,8 +281,6 @@ private VaadinServletContextInitializer getStubbedVaadinServletContextInitialize
return null;
}).when(servletContext).addListener(Mockito.any(ServletContextListener.class));

PowerMockito.doNothing()
.when(ServletDeployer.class);
ServletDeployer.logAppStartupToConsole(Mockito.any(), Mockito.anyBoolean());

return vaadinServletContextInitializerMock;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.vaadin.flow.spring.router;

import java.util.ArrayList;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.ErrorParameter;
import com.vaadin.flow.router.Location;
import com.vaadin.flow.router.NotFoundException;
import com.vaadin.flow.router.RouteNotFoundError;
import com.vaadin.flow.router.Router;
import com.vaadin.flow.server.RouteRegistry;
import com.vaadin.flow.server.VaadinSession;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.SpringBootVersion;

import static org.mockito.Mockito.when;

public class SpringRouteNotFoundErrorTest {

@Mock
private BeforeEnterEvent event;

@Mock
private ErrorParameter<NotFoundException> parameter;

@Mock
private UI ui;

@Mock
private VaadinSession session;

@Mock
private DeploymentConfiguration configuration;

@Mock
private Router router;

@Mock
private RouteRegistry registry;
private AutoCloseable autoCloseable;

@Before
public void setup() {
autoCloseable = MockitoAnnotations.openMocks(this);
when(event.getLocation()).thenReturn(new Location("foobar"));
when(parameter.hasCustomMessage()).thenReturn(false);
when(event.getUI()).thenReturn(ui);
when(ui.getSession()).thenReturn(session);
when(session.getConfiguration()).thenReturn(configuration);
when(configuration.isProductionMode()).thenReturn(false);
when(event.getSource()).thenReturn(router);
when(router.getRegistry()).thenReturn(registry);
when(registry.getRegisteredRoutes()).thenReturn(new ArrayList<>());
}

@After
public void teardown() throws Exception {
autoCloseable.close();
}

@Test
public void testRouteNotFoundError_bootVersionIsTwoFourOrNewer_noAddedMessageShown() {
final RouteNotFoundError normalErrorView = new RouteNotFoundError();
final SpringRouteNotFoundError springRouteNotFoundError = new SpringRouteNotFoundError();

normalErrorView.setErrorParameter(event, parameter);
// the project uses 2.4.0 at the time of writing this test, so it will
// not add the message
springRouteNotFoundError.setErrorParameter(event, parameter);

Assert.assertEquals("Invalid number of children", ((Long) normalErrorView.getChildren().count()).intValue(), ((Long) springRouteNotFoundError.getChildren().count()).intValue());
}

@Test
public void testRouteNotFoundError_bootVersionIsTwoTwoSomething_addedMessageShown() {
final RouteNotFoundError normalErrorView = new RouteNotFoundError();
final SpringRouteNotFoundError springRouteNotFoundError = new SpringRouteNotFoundError();

try (MockedStatic<SpringBootVersion> theMock = Mockito.mockStatic(SpringBootVersion.class)) {
theMock.when(SpringBootVersion::getVersion).thenReturn("2.2.0.RELEASE");

normalErrorView.setErrorParameter(event, parameter);
springRouteNotFoundError.setErrorParameter(event, parameter);

theMock.verify(SpringBootVersion::getVersion);
}
Assert.assertNotEquals("Invalid number of children", ((Long) normalErrorView.getChildren().count()).intValue(), ((Long) springRouteNotFoundError.getChildren().count()).intValue());
}

@Test
public void testRouteNotFoundError_productionMode_SpringVersionNotChecked() {
final SpringRouteNotFoundError springRouteNotFoundError = new SpringRouteNotFoundError();

try (MockedStatic<SpringBootVersion> theMock = Mockito.mockStatic(SpringBootVersion.class)) {
when(configuration.isProductionMode()).thenReturn(true);
theMock.when(SpringBootVersion::getVersion).then(AssertionError::new);
springRouteNotFoundError.setErrorParameter(event, parameter);
theMock.verifyNoInteractions();
}
}
}

0 comments on commit f04b15f

Please sign in to comment.