I have problem with #Bean WebDriver and How run sevral tests using #Autowrited - spring

Please, promt me. The main idea is create project with Selenium + Spring Boot.
I created it, created test#1 and test#2. If I run test#1, it is okay, after test#1 I want to run test#2, but doing it I have a probem with driver. If I am not mistaken, the reason is #Bean, I created WebDriver using #Bean. I think that problem is the Autowrited Bean with WebDriver, Pls tell me, How create Autowrited Bean for several test.
enter image description here
My Code:1) WebDriver config with Bean
#Configuration
public class WebDriverConfig {
#Bean
#ConditionalOnMissingBean
#ConditionalOnProperty(name = "browser", havingValue = "chrome")
#Primary
public WebDriver chromeDriver() {
WebDriverManager.chromedriver().setup();
return new ChromeDriver();
}
2)BasePage
#Component
public abstract class BasePage {
#Autowired
protected WebDriver driver;
protected final static long WAIT_TIME = 20;
#PostConstruct
private void init() {
PageFactory.initElements(this.driver, this);
}
3)My HomePage with test methods
#Component
public class HomePage extends BasePage {
#FindBy(xpath = "//a[#class='login_btn circle']")
private WebElement singIn;
#FindBy(xpath = "//*[#id='login_input1']")
private WebElement inputLogin;
#FindBy(xpath = "//*[#id='login_input2']")
private WebElement inputPassword;
#FindBy(css = "#login_submit")
private WebElement loginSubmit;
#FindBy(xpath = "//a[.//*[#id='anime_id_17']]")
private WebElement tg;
#FindBy(xpath = "//a[.//*[#id='anime_id_12']]")
private WebElement codeGias;
public HomePage goToHomePage(String url) {
driver.get(url);
waitForPageLoadComplete(WAIT_TIME);
return this;
}
public HomePage goToLogin(String name, String password) {
singIn.click();
waitForPageLoadComplete(WAIT_TIME);
inputLogin.clear();
inputLogin.sendKeys(name);
inputPassword.clear();
inputPassword.sendKeys(password);
loginSubmit.click();
waitForPageLoadComplete(WAIT_TIME);
tg.click();
waitForPageLoadComplete(WAIT_TIME);
return this;
}
public HomePage test(){
waitForPageLoadComplete(WAIT_TIME);
codeGias.click();
waitForPageLoadComplete(WAIT_TIME);
return this;
}
My BaseTest
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = SpringSelApplication.class)
public class BaseTest {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
public WebDriver driver;
#Autowired
public ApplicationContext applicationContext;
#BeforeEach
public void setup() {
driver.manage().window().maximize();
}
#AfterEach
public void teardown() {
driver.quit();
}
}

Consider the following fixes:
You don't have to use #ExtendWith, the chances are that you're running a reasonably recent version of spring boot and #SpringBootTest is already annotated with #ExtendWith annotation internally
A BasePage class, being abstract is not a #Component by itself.
When using a #SpringBootTest, make sure that you pass the class of the configuration that loads the webdriver. Spring boot, when sees a parameter to the #SpringBootTest doesn't load the whole application and instead loads whatever the the specified configuration class defines. Also since this way talks about "specific" configuration class to be loaded, it stops obeying the usual component scanning rules, so your web page might not be loaded, after all you're mixing configuration styles (you have both classes annotated with #Configuration and those using declarative approach - annotated with #Component)
Consider using #ContextConfiguration instead of #SpringBootTest if you have a plain case of configuration loading and don't really load the whole application

Related

Passing an external property to JUnit's extension class

My Spring Boot project uses JUnit 5. I'd like to setup an integration test which requires a local SMTP server to be started, so I implemented a custom extension:
public class SmtpServerExtension implements BeforeAllCallback, AfterAllCallback {
private GreenMail smtpServer;
private final int port;
public SmtpServerExtension(int port) {
this.port = port;
}
#Override
public void beforeAll(ExtensionContext extensionContext) {
smtpServer = new GreenMail(new ServerSetup(port, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
}
#Override
public void afterAll(ExtensionContext extensionContext) {
smtpServer.stop();
}
}
Because I need to configure the server's port I register the extension in the test class like this:
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class EmailControllerIT {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Value("${spring.mail.port}")
private int smtpPort;
#RegisterExtension
// How can I use the smtpPort annotated with #Value?
static SmtpServerExtension smtpServerExtension = new SmtpServerExtension(2525);
private static final String RESOURCE_PATH = "/mail";
#Test
public void whenValidInput_thenReturns200() throws Exception {
mockMvc.perform(post(RESOURCE_PATH)
.contentType(APPLICATION_JSON)
.content("some content")
).andExpect(status().isOk());
}
}
While this is basically working: How can I use the smtpPort annotated with #Value (which is read from the test profile)?
Update 1
Following your proposal I created a custom TestExecutionListener.
public class CustomTestExecutionListener implements TestExecutionListener {
#Value("${spring.mail.port}")
private int smtpPort;
private GreenMail smtpServer;
#Override
public void beforeTestClass(TestContext testContext) {
smtpServer = new GreenMail(new ServerSetup(smtpPort, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
};
#Override
public void afterTestClass(TestContext testContext) {
smtpServer.stop();
}
}
The listener is registered like this:
#TestExecutionListeners(value = CustomTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
When running the test the listener gets called but smtpPort is always 0, so it seems as if the #Value annotation is not picked up.
I don't think you should work with Extensions here, or in general, any "raw-level" JUnit stuff (like lifecycle methods), because you won't be able to access the application context from them, won't be able to execute any custom logic on beans and so forth.
Instead, take a look at Spring's test execution listeners abstraction
With this approach, GreenMail will become a bean managed by spring (probably in a special configuration that will be loaded only in tests) but since it becomes a bean it will be able to load the property values and use #Value annotation.
In the test execution listener you'll start the server before the test and stop after the test (or the whole test class if you need that - it has "hooks" for that).
One side note, make sure you mergeMode = MergeMode.MERGE_WITH_DEFAULTS as a parameter to #TestExecutionListeners annotation, otherwise some default behaviour (like autowiring in tests, dirty context if you have it, etc) won't work.
Update 1
Following Update 1 in the question. This won't work because the listener itself is not a spring bean, hence you can't autowire or use #Value annotation in the listener itself.
You can try to follow this SO thread that might be helpful, however originally I meant something different:
Make a GreenMail a bean by itself:
#Configuration
// since you're using #SpringBootTest annotation - it will load properties from src/test/reources/application.properties so you can put spring.mail.port=1234 there
public class MyTestMailConfig {
#Bean
public GreenMail greenMail(#Value(${"spring.mail.port"} int port) {
return new GreenMail(port, ...);
}
}
Now this configuration can be placed in src/test/java/<sub-package-of-main-app>/ so that in production it won't be loaded at all
Now the test execution listener could be used only for running starting / stopping the GreenMail server (as I understood you want to start it before the test and stop after the test, otherwise you don't need these listeners at all :) )
public class CustomTestExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.start();
}
#Override
public void afterTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.stop();
}
}
Another option is autowiring the GreenMail bean and using #BeforeEach and #AfterEach methods of JUnit, but in this case you'll have to duplicate this logic in different Test classes that require this behavour. Listeners allow reusing the code.

Spring 2 + JUnit 5, share #MockBean for entire test suite

I create a Spring 2.3 application using Spring Data REST, Hibernate, Mysql.
I created my tests, I've around 450 tests splitted in about 70 files. Because the persistence layer leans on a multi tenant approach (single db per tenant) using a Hikari connection pool, I've the need to avoid the pool is initializated for each test file but at the same time I need to use #MockBean because I need to mock up some repositories in the entire Spring test contest.
I create a custom annotation for all test in my suite:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#SpringBootTest
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public #interface TestConfig {
}
Reading many posts and the doc, I know if I use #MockBean inside a test, the Spring context is reloaded and therefore a new pool connection is created in my case.
My idea is to create a #MockBean and share it with all tests in my suite so the context is not reloaded every time.
I tried several approaches:
#Log4j2
public class TestExecutionListener extends AbstractTestExecutionListener implements Ordered {
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
try {
TestDbUtils testDbUtils = (TestDbUtils) testContext.getApplicationContext().getBean(TestDbUtils.class);
testDbUtils.truncateDB();
TenantRepository tenantRepository = mock(TenantRepository.class);
testContext.setAttribute("tenantRepository", tenantRepository);
TenantContext.setCurrentTenantId("test");
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
} catch (Exception e) {
}
}
#Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
All my tests are annotated like this:
#TestConfig
#Log4j2
public class InvoiceTests {
#Test
public void test1(){
}
}
Unfortunately my tenantRepository.findByTenantId() is not mocked up. I also tried to create an abstract superclass:
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest {
#MockBean
protected TenantRepository tenantRepository;
#MockBean
protected SubscriptionRepository subscriptionRepository;
#Autowired
protected TestDbUtils testDbUtils;
#BeforeAll
public void beforeAll() {
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
}
#BeforeEach
public void setup() {
testDbUtils.truncateDB();
TenantContext.setCurrentTenantId("test");
}
}
Even if my tests extended this superclass, during the run all of them were skipped (not sure why).
Is there any way to accomplish what I described?

WebMvcTest is too greedy

I want to write a WebMvcTest test for a single controller in my Spring Boot application. Among other things there are some custom Converters in my application. Although they are not needed for this particular controller that I want to test, Spring tries to create them anyway.
Now the problem: those custom converters require more beans from my application which are not initialised by WebMvcTest test slice. And don't want to mock tens of beans which are completely irrelevant for the particular test. Apart from specifying them all manually in excludeFilters, what are best practises for excluding some web components from specific WebMvcTest tests?
You could use a custom exclude filter in order to avoid loading converters into application context:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = YourController.class, excludeFilters = #ComponentScan.Filter(type = CUSTOM, classes = NoConvertersFilter.class))
public class YourControllerTest {
...
}
class NoConvertersFilter extends TypeExcludeFilter {
private static final String CONVERTER_INTERFACE_NAME = Converter.class.getName();
#Override
public boolean match(#NonNull final MetadataReader metadataReader, #NonNull final MetadataReaderFactory metadataReaderFactory) throws IOException {
return Arrays.asList(metadataReader.getClassMetadata().getInterfaceNames()).contains(CONVERTER_INTERFACE_NAME);
}
}
With this approach you just have to add the excludeFilter to those controllers in which you don't want to have Converters loaded. No worries if a new converter is added: it'll be automatically excluded as far as it implements the converter interface.
For custom tests don't use WebMvcTest, create a custom configuration:
#SpringBootTest
#WebAppConfiguration
#RunWith(value = SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {SomeYourTestConfiguration.class})
public class TestClass {
private MockMvc mockMvc;
#Before
public void setup() {
var someController = new SomeController();
mockMvc = MockMvcBuilders.standaloneSetup(someController).addFilters(...)
.setMessageConverters(...).setControllerAdvice(...).setValidator(...);
}
#Test
public void test() {
//arrange
when(...).thenReturn(...);
//act
var response = mockMvc.perform(...).andReturn().getResponse();
//assert
...
}
}
You can configure your mockMvc how you want.

spring-boot: Application loads but tests fail

I am experiencing rather strange thing when using Spring Boot. Lets get with it.
I have an app which, when ran from spring-boot:run, loads perfectly fine and I can use my server. However, if I try to run tests (either via launching test from IntelliJ or via surefire plugin) context fails to load.
Issue lies within this class (only relevant part shown):
#RestController
#RequestMapping(
value = "/sa/revisions/"
)
#SuppressWarnings("unchecked")
class RevisionController {
#Autowired
// cant autowire this field
private RepositoryEntityLinks repositoryEntityLinks = null;
/* omitted */
}
And here is my main class:
#EnableAsync
#EnableCaching
#EnableAutoConfiguration
#EnableConfigurationProperties
#Import({
SecurityConfiguration.class,
DataConfiguration.class,
RestConfiguration.class
})
public class SpringAtomApplication {
#Autowired
private DataLoaderManager dataLoaderManager = null;
public static void main(String[] args) {
SpringApplication.run(SpringAtomApplication.class, args);
}
#Bean
public CacheManager cacheManager() {
final GuavaCacheManager manager = new GuavaCacheManager();
manager.setAllowNullValues(false);
return manager;
}
#PostConstruct
private void doPostConstruct() {
this.dataLoaderManager.doLoad();
}
}
As I said, application loads without an issue when ran normally, however when it comes to this simple test, everything falls apart:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SpringAtomApplication.class)
public class SpringAtomApplicationTests {
#Test
public void contextLoads() {
}
}
Would appreciate any suggestion, because I'd love to start with testing it.
You should set SpringApplicationContextLoader in your test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(
classes = SpringAtomApplication.class,
loader = SpringApplicationContextLoader.class)
public class SpringAtomApplicationTests {
#Test
public void contextLoads() {
}
}
With that you can test non-web features (like a repository or a service) or start an fully-configured embedded servlet container and run your tests using MockMvc.
Reference: http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/SpringApplicationContextLoader.html

Mocking a service within service (JUnit)

I have the following service:
#Service
public class PlayerValidationService {
#Autowire
private EmailService emailService;
public boolean validatePlayerEmail(Player player) {
return this.emailService.validateEmail(player.getEmail());
}
Now in my junit test class i'm using a different 3rd service that uses PlayerValidationService :
public class junit {
#autowire PlayerAccountService playerAccountService ;
#Test
public test() {
this.playerAccountService .createAccount();
assertAllSortsOfThings();
}
Is it possible to mock the EmailService within the PlayerAccountService when using annotation based autowiring? (for example make the mock not checking the validation of the email via the regular email provider we work with)
thanks.
There are a couple of ways in which you could do this. First the simplest option is to ensure that your service provides a setEmailService(EmailService) method. In which case you just replace the Spring-injected implementation with your own.
#Autowired
private PlayerValidationService playerValidationService;
#Mock
private EmailService emailService;
#Before
public void setup() {
initMocks(this);
playerValidationService.setEmailService(emailService);
}
A shortcoming of that approach is that an instance of the full-blown EmailService is likely to be created by Spring. Assuming that you don't want that to happen, you can use 'profiles'.
In your test packages, create a configuration class which is only active in a particular profile:
#Configuration
#Profile("mockemail")
public class MockEmailConfig {
#Bean(name = "emailService")
public EmailService emailService() {
return new MyDummyEmailService();
}
}
And add an annotation to your test to activate that profile:
#ActiveProfiles({ "mockemail" })
public class PlayerValidationServiceTest {
//...
}

Resources