I want to use Testcontainers with #DataJpaTest (and #SpringBootTest) using JUnit 5. I have the basic setup working using the #Testcontainers and #Container annotation like this:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Testcontainers
public class AtleteRepositoryTest {
#Container
private static final PostgreSQLContainer<?> CONTAINER = new PostgreSQLContainer<>("postgres:11");
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", CONTAINER::getJdbcUrl);
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}
#Autowired
private AtleteRepository repository;
#Test
void testSave() {
repository.save(new Atlete("Wout Van Aert", 0, 1, 0));
assertThat(repository.count()).isEqualTo(1);
}
}
See https://github.com/wimdeblauwe/blog-example-code/tree/feature/testcontainers-datajpatest/testcontainers-datajpatest for the full example code (AtleteRepositoryTest, TeamRepositoryTest and TestcontainersDatajpatestApplicationTests).
To avoid the repetition of declaring the PostgreSQL container and the dynamic properties, I tried the following:
JUnit 5 extension
Baeldung has a blog about how you can use a JUnit 5 extension to avoid the duplication.
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.testcontainers.containers.PostgreSQLContainer;
public class PostgreSQLExtension implements BeforeAllCallback, AfterAllCallback {
private PostgreSQLContainer<?> postgres;
#Override
public void beforeAll(ExtensionContext context) {
postgres = new PostgreSQLContainer<>("postgres:11");
postgres.start();
System.setProperty("spring.datasource.url", postgres.getJdbcUrl());
System.setProperty("spring.datasource.username", postgres.getUsername());
System.setProperty("spring.datasource.password", postgres.getPassword());
}
#Override
public void afterAll(ExtensionContext context) {
postgres.stop();
}
}
It works if you only have 1 test, but not if you run multiple at the same time (using IntelliJ or with Maven). In that case, one of the tests will fail because there is no connection with the database that can be made.
Also note that this extension does not use the DynamicPropertyRegistry, but plain environment variables.
See the feature/testcontainers-datajpatest_baeldung-extension branch for the code.
Using a common superclass
On the branch feature/testcontainers-datajpatest_database-base-test, I tried using a common superclass:
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
public class DatabaseBaseTest {
private static final PostgreSQLContainer<?> CONTAINER = new PostgreSQLContainer<>("postgres:11");
#BeforeAll
static void start() {
CONTAINER.start();
}
#AfterAll
static void stop() {
CONTAINER.stop();
}
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", () -> {
String jdbcUrl = CONTAINER.getJdbcUrl();
System.out.println("jdbcUrl = " + jdbcUrl);
return jdbcUrl;
});
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}
}
Unfortunately that also does not work. I noticed in the logging that the #DynamicPropertySource annotated method was only called once and not for each test, which led me to try option 3:
Common superclass with #DynamicPropertySource in subclasses
When using the common superclass, but adding the #DynamicPropertySource method in each subclass, it works again.
Example code of such a subclass:
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class AtleteRepositoryTest extends DatabaseBaseTest {
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", () -> {
String jdbcUrl = CONTAINER.getJdbcUrl();
System.out.println("jdbcUrl = " + jdbcUrl);
return jdbcUrl;
});
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}
#Autowired
private AtleteRepository repository;
#Test
void testSave() {
repository.save(new Atlete("Wout Van Aert", 0, 1, 0));
assertThat(repository.count()).isEqualTo(1);
}
}
See branch feature/testcontainers-datajpatest_database-base-test_subclasses for that version.
So while it works, there is still a lot of duplication in each test class.
Are there any other options for avoiding the duplication?
To avoid Testcontainers code repetition I generally follow 2 approaches:
Using ApplicationContextInitializer with #ContextConfiguration
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.testcontainers.containers.PostgreSQLContainer;
#Slf4j
public class PostgreSQLContainerInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static PostgreSQLContainer sqlContainer = new PostgreSQLContainer("postgres:10.7");
static {
sqlContainer.start();
}
public void initialize (ConfigurableApplicationContext configurableApplicationContext){
TestPropertyValues.of(
"spring.datasource.url=" + sqlContainer.getJdbcUrl(),
"spring.datasource.username=" + sqlContainer.getUsername(),
"spring.datasource.password=" + sqlContainer.getPassword()
).applyTo(configurableApplicationContext.getEnvironment());
}
}
import com.sivalabs.myservice.common.PostgreSQLContainerInitializer;
import com.sivalabs.myservice.entities.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ContextConfiguration;
import javax.persistence.EntityManager;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
#DataJpaTest
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = {PostgreSQLContainerInitializer.class})
class UserRepositoryTest {
#Autowired
EntityManager entityManager;
#Autowired
private UserRepository userRepository;
#Test
void shouldReturnUserGivenValidCredentials() {
User user = new User(null, "test#gmail.com", "test", "Test");
entityManager.persist(user);
Optional<User> userOptional = userRepository.login("test#gmail.com", "test");
assertThat(userOptional).isNotEmpty();
}
}
Using #DynamicPropertySource in Java 8+ Interface
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
#Testcontainers
public interface PostgreSQLContainerInitializer {
#Container
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:12.3");
#DynamicPropertySource
static void registerPgProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
#DataJpaTest
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest implements PostgreSQLContainerInitializer {
....
....
}
With these approaches we don't have to repeat PostgreSQLContainer declarations and Spring property settings.
Whether to use PostgreSQLContainer as a static field or not depends on whether you want to spin up a new container for every test or 1 container per test class.
PS:
I avoided using common base class approach because sometime one test needs only 1 container and another test needs multiple containers. If we follow add all the containers in common base class then for every test/class all those containers will be started irrespective of their usage which makes tests very slow.
Related
Intro:
Our product needs to have integrations tests for 3 different databases:
Oracle
Postgres
MSSQL
We are using Spring Boot as our framework and TestContainers to start up the databases mentioned above.
The problem:
We need to run the same tests for each container (database).
After a lot of digging on the net the only way that I could think of was using a BaseClass where we write all the test cases and for each container, we create a class that inherits from the BaseClass and we override the method and annotate it with #Test.
Below in the code, you will a single JUnit5 extension for Postgres that starts a TestContainer, base test class, and a test class that gets extended from the Postgres extension, starts a Spring Application context, and runs the tests from the base class.
The code:
import com.company.itest.AutoConfig;
import com.company.itest.BaseIntegrationTest;
import com.company.itest.db.mssql.MSSqlTest;
import com.company.itest.db.oracle.OracleTest;
import com.company.itest.db.postgres.PostgresTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
public class TestTheTest extends BaseIntegrationTest {
public void contextLoads() {
Assertions.assertEquals(1, 1);
}
public void contextLoads2() {
Assertions.assertNotEquals(1, 2);
}
}
#SpringBootTest(
classes = AutoConfig.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#PostgresTest
class TestPostgres extends TestTheTest {
#Test
public void contextLoads() {
super.contextLoads();
}
#Test
public void contextLoads2() {
super.contextLoads2();
}
}
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.PostgreSQLContainer;
public class PostgresqlTestContainersExtension implements BeforeAllCallback, AfterAllCallback {
private final Logger log = LoggerFactory.getLogger(PostgresqlTestContainersExtension.class);
private PostgreSQLContainer<?> postgres;
#Override
public void beforeAll(ExtensionContext context) {
log.info("Setting up postgres container");
postgres = new PostgreSQLContainer<>("postgres:13").withReuse(true);
postgres.start();
System.setProperty("spring.datasource.url", postgres.getJdbcUrl());
System.setProperty("spring.datasource.username", postgres.getUsername());
System.setProperty("spring.datasource.password", postgres.getPassword());
}
#Override
public void afterAll(ExtensionContext context) {
postgres.stop();
}
}
package com.company.itest.db.postgres;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#ExtendWith(SpringExtension.class)
#ExtendWith({PostgresqlTestContainersExtension.class})
#Testcontainers
public #interface PostgresTest {}
The question:
How can I create a single JUnit test class and then rerun it with a different JUnit5 extension without doing this polymorphism?
If you are using maven, you could try to have a different profile per db
In our service, we are initializing a bean (say "A") and that internally constructing a CacheableService Object by using - new CacheableService(). And as I know spring's #Cacheable annotations won't work on class method if the class is initialized using "new" Keyword.
Then what is an alternative or a way to cache method response?
Scenario :
<bean class="com.package.src.A"/>
public class A {
Map<String, CacheableService> map;
public CacheableService2() {
map = new HashedMap();
map.put("a", new CacheableService());
}
}
import org.springframework.cache.annotation.Cacheable;
public class CacheableService {
#Cacheable(value = "entityCount", key = "#criteria.toString()")
public int someEntityCount(final String criteria) {
System.out.println("Inside function : " + criteria);
return 5;
}
}
Here is a minimum example which demonstrates caching using Spring Boot. The code for the examples below can be found here.
Go to https://start.spring.io/ and create a new Spring Boot project. Make sure to include "Spring cache abstraction" which results in this entry being added to your pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
Add the #EnableCaching annotation to your application:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
#EnableCaching
#SpringBootApplication
public class CacheableApplication {
public static void main(String[] args) {
SpringApplication.run(CacheableApplication.class, args);
}
}
Your service:
package com.example;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
#Service
public class CacheableService {
#Cacheable(value = "entityCount")
public int someEntityCount(final String criteria) {
System.out.print(String.format("Inside function: %s", criteria));
return 5;
}
}
Class A:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class A {
private CacheableService cacheableService;
public A(#Autowired CacheableService cacheableService) {
this.cacheableService = cacheableService;
}
public int getEntityCount(String criteria) {
return cacheableService.someEntityCount(criteria);
}
}
And then here is a test that demonstrates that the caching is working. As you can see in the test a.getEntityCount("foo") is being called twice, but in standard out we only see "Inside function: foo" being printed once. Therefore we have verified that the second call resulted in the cache being used to produce the result.
package com.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
class CacheableTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
#Autowired
private A a;
#BeforeEach
public void init() {
System.setOut(new PrintStream(outContent));
}
#Test
public void testCaching() {
a.getEntityCount("foo");
a.getEntityCount("foo");
assertEquals("Inside function: foo", outContent.toString());
}
}
EDIT:
If you want to move the cache outside of the Spring lifecycle and manually manage it then I would recommend using Caffeine. Here is the same example but now without any Spring involved.
Your service:
package com.example.withoutspring;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class CaffeineCachingService {
private LoadingCache<String, Integer> entityCountCache = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> someEntityCount(key));
public int cachedEntityCount(final String criteria) {
return entityCountCache.get(criteria);
}
private int someEntityCount(final String criteria) {
System.out.print(String.format("Inside function: %s", criteria));
return 5;
}
}
Class B:
package com.example.withoutspring;
public class B {
private CaffeineCachingService cacheableService;
public B() {
cacheableService = new CaffeineCachingService();
}
public int getEntityCount(String criteria) {
return cacheableService.cachedEntityCount(criteria);
}
}
And the same test but without Spring:
package com.example.withoutspring;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CaffeineCacheableTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private B b = new B();
#BeforeEach
public void init() {
System.setOut(new PrintStream(outContent));
}
#Test
public void testCaching() {
b.getEntityCount("foo");
b.getEntityCount("foo");
assertEquals("Inside function: foo", outContent.toString());
}
}
Obviously you need to tune the cache to perform how you want it so probably evicting the cached values after 5 minutes is not what you want but if you visit the Caffeine Github page you will see a lot of detailed examples how to configure the cache to meet your use-case.
Hope this helps!
I have a ServiceWebClientInterface.java like this
import reactor.core.publisher.Mono;
public interface ServiceWebClientInterface {
Mono<String> apiCall();
}
MyClass.java
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
public class MyClass extends AbstractGatewayFilterFactory<MyClass.Config> {
private final ServiceWebClientInterface serviceWebClientInterface;
MyClass(final ServiceWebClientInterface serviceWebClientInterface) {
this.serviceWebClientInterface = serviceWebClientInterface;
}
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
return serviceWebClientInterface.apiCall().flatMap(response -> {
if (!"Valid".equals(response)) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
});
};
}
public static class Config {
// Put the configuration properties
}
}
I'm trying to unit test myMethod using StepVerifier, but I am not able to execute statements inside the inner lambda function of myMethod.
MyClassTest.java
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
class MyClassTest {
#Mock
ServiceWebClientInterface mockServiceWebClientInterface;
#Mock
private ServerWebExchange mockServerWebExchange;
#Mock
private GatewayFilterChain mockGatewayFilterChain;
#Mock
private ServerHttpResponse mockServerHttpResponse;
#BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
void test_apply_forValid() {
when(mockServiceWebClientInterface.apiCall()).thenReturn(Mono.just("Valid"));
MyClass.Config config = new MyClass.Config();
MyClass myClass = new MyClass(mockServiceWebClientInterface);
GatewayFilter gatewayFilter = myClass.apply(config);
Mono<Void> response = gatewayFilter.filter(mockServerWebExchange, mockGatewayFilterChain);
StepVerifier.create(response).expectComplete();
verify(mockServiceWebClientInterface).apiCall();
verify(mockGatewayFilterChain).filter(mockServerWebExchange);
}
#Test
void test_apply_forInValid() {
when(mockServiceWebClientInterface.apiCall()).thenReturn(Mono.just("InValid"));
when(mockServerWebExchange.getResponse()).thenReturn(mockServerHttpResponse);
MyClass.Config config = new MyClass.Config();
MyClass myClass = new MyClass(mockServiceWebClientInterface);
GatewayFilter gatewayFilter = myClass.apply(config);
Mono<Void> response = gatewayFilter.filter(mockServerWebExchange, mockGatewayFilterChain);
StepVerifier.create(response).expectComplete();
verify(mockServiceWebClientInterface).apiCall();
verify(mockServerHttpResponse).setStatusCode(eq(HttpStatus.FORBIDDEN));
verify(mockServerHttpResponse).setComplete();
verify(mockGatewayFilterChain, never()).filter(mockServerWebExchange);
}
}
Please find the complete code above, When I run the tests I observe that the inner lambda function does not get invoked using the step verifier.
I guess you want to test the class that implements MyLambda interface.
For sure you inject there serviceWebClientInterface as mentioned on code snippet.
To unit test that class, you should mock the serviceWebClientInterface.apiCall() and verify if it was called. As an addition to your actual code snippet.
You can use Mockito library for that purpose.
create a mock:
given(serviceWebClientInterface).willReturn(Mono.just("some text"));
then verify if it is called:
verify(serviceWebClientInterface).apiCall()
I was able to fix this issue by using
StepVerifier.create(response).verifyComplete();
and mocking chain.filter(exchange);
I am trying to run tests on a controller class and it has atleast a method which internally uses a DAO to retrieve information from the DB (MySQL).
The problem I have is that the dao method is giving null and I end up with NullPointerException error.
How do I tests a class which has methods that internally use a database connection?
Haven't been able to find any useful post/answer.
Project structure:
src
main
java
[package]
RegisterController.java
Config.java // configuration class
test
java
[package]
RegisterControllerTest.java
RegisterController.java
package com.webapp.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.webapp.Config;
import com.webapp.dao.AccountDao;
#Controller
public class RegisterController {
#Autowired
AccountDao accDao;
public String validateUsername(String uname){
List<String> errors = new ArrayList<String>();
// ... unrelated code
// NullPointerException thrown here
if(accDao.getAccountByUsername(uname) != null)
errors.add("err#taken");
return errors.toString();
}
}
RegisterControllerTest.java
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import com.webapp.controller.RegisterController;
import com.webapp.Config;
#SpringBootTest
public class RegisterControllerTest {
#Mock
private Config config;
private RegisterController rc;
#BeforeEach
public void init() {
config = Mockito.mock(Config.class);
rc = new RegisterController();
}
#Test
public void testValidateUsername() {
assertEquals("[]", rc.validateUsername("Username123")); // N.P.E
}
}
Config.java
package com.webapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import com.webapp.dao.AccountDao;
import com.webapp.dao.AccountDaoImpl;
import com.webapp.dao.Dao;
#Configuration
#ComponentScan(basePackages = { "com.webapp.controller", "com.webapp.dao", "com.webapp.test" })
public class Config {
private static class Database {
private static String host = "127.0.0.1";
private static String user = "root";
private static String pass = "root";
private static String dbname = "memedb";
private static int port = 3306;
public static String getUrl() {
return "jdbc:mysql://"+host+":"+port+"/"+dbname+"?serverTimezone=Europe/Stockholm";
}
}
#Bean
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl(Database.getUrl());
ds.setUsername(Database.user);
ds.setPassword(Database.pass);
return ds;
}
#Bean
public AccountDao getAccDao() {
return new AccountDaoImpl(getDataSource());
}
}
Instead of mocking config, mock the Dao. You are getting NPE, because the mocked config is not setting #Autowired accDao... so your accDao == null:
#Controller
public class RegisterController {
AccountDao accDao;
public RegisterController(AccountDao accDao) {
this.accDao = accDao;
}
...
}
#BeforeEach
public void init() {
accDaoMock = Mockito.mock(AccountDao.class);
rc = new RegisterController(accDaoMock);
}
#Test
public void testValidateUsername() {
when(accDaoMock.getAccountByUsername("Username123")).thenReturn(null);
assertEquals("[]", rc.validateUsername("Username123"));
}
Why are you configuring the connection DB programmatically? I advise you configure the connection DB with autoconfiguration of Spring Boot.
The DAO object have to mock in an unit test.
Here is a good article about the Junit test for a Spring Boot App.
I try to improve my Spring knowledge by reading Spring in action 4.
When I've to to section, describing using of Qualifier annotation (3.3.2), i faced the problem.
To test this annotation in action, I wrote Dessert interface, which is implemented by 3 classes, creating in context using #Component annotation.
I also created class Taster, which "tastes" some dessert, autowired into by some qualifier.
When I run my application, using AnnotationConfigApplicationContext - everything works good. With SpringJUnit4ClassRunner - it does not. I guess I miss something in my test code, but I do not have enough knowledge to realize what.
Interface:
package bakery.intrface;
#FunctionalInterface
public interface Dessert {
void introduce();
}
Cake:
package bakery.desserts;
import bakery.intrface.Dessert;
import org.springframework.stereotype.Component;
#Component
public class Cake implements Dessert {
#Override
public void introduce() {
System.out.println("I am a cake!");
}
}
Cookie:
package bakery.desserts;
import bakery.intrface.Dessert;
import org.springframework.stereotype.Component;
#Component
public class Cookie implements Dessert {
#Override
public void introduce() {
System.out.println("I'm a cookie!");
}
}
Ice cream:
package bakery.desserts;
import bakery.intrface.Dessert;
import org.springframework.stereotype.Component;
#Component
public class IceCream implements Dessert {
#Override
public void introduce() {
System.out.println("I'm an ice cream!");
}
}
The class, consumes some bean, Taster:
package bakery;
import bakery.intrface.Dessert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
#Component
public class Taster {
private Dessert dessert;
public void taste(){
dessert.introduce();
}
#Autowired
#Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
}
Configuration:
package bakery.config;
import bakery.Bakery;
import bakery.Taster;
import bakery.desserts.Cake;
import bakery.intrface.Dessert;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
#ComponentScan(basePackageClasses = Bakery.class)
public class BakeryConfig {
}
Run class:
package bakery;
import bakery.config.BakeryConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Bakery {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BakeryConfig.class);
String[] beans = context.getBeanDefinitionNames();
Taster taster = (Taster) context.getBean("taster");
taster.taste();
}
}
Test class:
package bakery;
import bakery.config.BakeryConfig;
import bakery.intrface.Dessert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertNotNull;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = BakeryConfig.class)
public class BakeryTest {
#Autowired
Dessert dessert;
#Autowired
Taster taster;
#Test
public void contextInit(){
assertNotNull(dessert);
dessert.introduce();
}
#Test
public void tasterInit(){
assertNotNull(taster);
}
}
When I run the test, I'm getting the exception: No qualifying bean of type [bakery.intrface.Dessert] is defined: expected single matching bean but found 3: cookie,iceCream,cake.
There are 3 "Dessert" beans in your application context, you have to specify which one you want to wire.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = BakeryConfig.class)
public class BakeryTest {
#Autowired
#Qualifier("iceCream") // <===================== you must specify which bean to be wired
Dessert dessert;
#Autowired
Taster taster;
This is to be expected.
The declaration
#Autowired
Dessert dessert;
is asking for a Dessert object. Dessert is the interface, and there are three implementing classes, Cookie, IceCream, and Cake. Since you haven't made it more explicit which of those implementations you want, Spring throws an error because it can't decide what to do.
If you need this in your test, you can do one of the following:
#Autowired
#Qualifier("iceCream")
Dessert dessert;
to get only the ice cream dessert,
OR
#Autowired
List<Dessert> desserts;
to get a list containing all the implementations.