I am using Spring Boot and Spring Data Cassandra in my webflux application. I have issue when I do integration test with docker by using testcontainer, Spring doesn't inject the implementation of my cassandra repository in my tests but it always injects Mockito mock object.
For testing I have 2 kind of tests are unitTest and integrationTest
with properly profile test and integration.
Below are all my required classes and test classes
My cassandra repository using Spring Data Cassandra
#Repository
public interface UserGroupRepository extends CassandraRepository<UserGroup, String> {
}
For test profile
#ActiveProfiles("test")
#Configuration
public class TestConfig {
#MockBean
private CqlSession cassandraSession;
#MockBean
private CassandraConverter cassandraConverter;
#MockBean
public UserGroupRepository consumerSegmentRepository;
#MockBean
private CassandraMappingContext cassandraMapping;
#MockBean
private SessionFactoryFactoryBean cassandraSessionFactory;
#MockBean
private CassandraAdminTemplate cassandraAdminTemplate;
#MockBean
private SessionFactory sessionFactory;
}
For integration profile
My base integration test class which use docker for real cassandra database
public abstract class AbstractTest {
public static final String CASSANDRA_KEYSPACE = "user_group";
public static final String CASSANDRA_SCHEMA_SCRIPT = "cassandra/schema.cql";
public static final String CASSANDRA_IMAGE = "cassandra:3.11.2";
public static final int CASSANDRA_PORT = 9042;
private static final Logger log = LoggerFactory.getLogger(AbstractTest.class);
protected static final CassandraContainer CASSANDRA = new CassandraContainer(CASSANDRA_IMAGE);
static {
startCassandra();
}
protected static void startCassandra() {
CASSANDRA.withInitScript(CASSANDRA_SCHEMA_SCRIPT)
.withExposedPorts(CASSANDRA_PORT)
.withStartupTimeout(Duration.ofMinutes(5))
.withReuse(true)
.start();
System.setProperty("spring.data.cassandra.keyspace-name", CASSANDRA_KEYSPACE);
System.setProperty("spring.data.cassandra.contact-points", "localhost:" + CASSANDRA.getMappedPort(9042));
System.setProperty("spring.data.cassandra.local-datacenter", "datacenter1");
System.setProperty("spring.data.cassandra.schema-action", "create_if_not_exists");
}
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// Cassandra
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
"spring.data.cassandra.keyspace-name=" + CASSANDRA_KEYSPACE,
"spring.data.cassandra.contact-points=" + "localhost:" + CASSANDRA.getMappedPort(CASSANDRA_PORT),
"spring.data.cassandra.local-datacenter=" + "datacenter1",
"spring.data.cassandra.schema-action=" + "create_if_not_exists"
);
Runtime.getRuntime().addShutdownHook(new Thread(CASSANDRA::stop));
}
}
protected static Session getCassandraSession() {
return CASSANDRA.getCluster().connect(CASSANDRA_KEYSPACE);
}
}
My integration test
#ActiveProfiles("integration")
#AutoConfigureWebTestClient
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureDataCassandra
#ContextConfiguration(initializers = AbstractTest.Initializer.class)
#EnableCassandraRepositories(basePackageClasses = {UserGroupRepository.class})
public class UserGroupControllerTest extends AbstractTest {
private static final Logger log = LoggerFactory.getLogger(UserGroupControllerTest.class);
#Autowired
private WebTestClient webTestClient;
#Autowired
private UserGroupRepository userGroupRepository;
#Test
void test_cassandra_upAndRunning() {
assertThat(CASSANDRA.isRunning()).isTrue();
Session session = getCassandraSession();
ResultSet result = session.execute("select * from user_group where group_id='group001'");
Row row = result.iterator().next();
assertEquals("User group 1", row.getString("group_name"));
}
#Test
public void test_getCurrentUserGroup_success() {
log.info("Instance of userGroupRepository {}", userGroupRepository.getClass().getName());
UserGroupDto userGroupDto = webTestClient.get()
.uri("/api/v1/usergroups")
.header(HttpHeaders.AUTHORIZATION, "Bearer abc")
.exchange()
.expectStatus()
.isOk()
.expectBody(UserGroupDto.class)
.returnResult()
.getResponseBody();
Assertions.assertNotNull(userGroupDto);
}
}
The injection code
#Autowired
private UserGroupRepository userGroupRepository;
Spring always inject Mockito bean for this repository, althought I try to use #EnableCassandraRepositories and #AutoConfigureDataCassandra but it seems not work.
My question is how to force Spring to inject the real implementation of my cassandra repository? Thanks.
The root cause is invalid usage of ActiveProfile and Profile in Spring. Update TestConfig to use #Profile instead of #ActiveProfiles.
Related
Is it possible to use dependency injection with unit tests using Spring Boot? For integration testing #SpringBootTest start the whole application context and container services. But is it possible to enable dependency injection functionality at unit test granularity?
Here's the example code
#ExtendWith(SpringExtension.class)
public class MyServiceTest {
#MockBean
private MyRepository repo;
#Autowired
private MyService service; // <-- this is null
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
#Service
public class MyService {
private final MyRepository repo; // <-- this is null
public MyService(MyRepository repo) {
this.repo = repo;
}
public List<String> getData() {
return repo.findAll().stream()
.map(MyEntity::getData)
.collect(Collectors.toList());
}
}
Or should I just manage the SUT (service class) as POJO and manually inject the mocked dependencies? I want to keep tests fast but minimize boilerplate code.
As #M.Deinum mentioned in the comments, unit tests shouldn't use dependency injection. Mock MyRepository and inject MyService using Mockito (and Junit5):
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#InjectMocks
private MyService service;
#Mock
private MyRepository repo;
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
If you want to test the repository, use #DataJpaTest. From the docs:
Using this annotation will disable full auto-configuration and instead
apply only configuration relevant to JPA tests.
#DataJpaTest
public class MyRepositorTest {
#Autowired
// This is injected by #DataJpaTest as in-memory database
private MyRepository repo;
#Test
void testCount() {
repo.save(new MyEntity("hello"));
repo.save(new MyEntity("world"));
assertEquals(2, repo.count());
}
}
In conclusion, the suggested approach is to test the service layer mocking the repository layer with Mockito (or similar library) and to test the repository layer with #DataJpaTest.
You have not added the #Autowired in service for MyRepository
Service Class
#Service
public class MyService {
private final MyRepository repo; // <-- this is null
#Autowired
public MyService(MyRepository repo) {
this.repo = repo;
}
public List<String> getData() {
return repo.findAll().stream()
.map(MyEntity::getData)
.collect(Collectors.toList());
}
}
Service Test Class
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#Mock
private MyRepository repo;
#InjectMocks
private MyService service;
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
Trying to write an integration test for a Spring application. Say i've got a class A which contains a class B object. Class B contains a class C object and I need to mock an object within this class for the integration test - any idea how i go about doing that without passing every object through as a parameter in the constructor?
e.g.
#Service
Class A {
#Autowired
private B b;
public void testA() {
B.testB();
}
}
#Service
Class B {
#Autowired
private C c;
public void testB() {
c.testC();
}
}
#Service
Class C {
//External class pulled in from dependency library
#Autowired
private RestTemplate restTemplate;
public void testC() {
restTemplate.doSomethingInOutsideWorld();
}
}
Integration test:
#RunWith(JUnitParamsRunner.class)
#SpringBootTest
public class MyIt {
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Mock
private RestTemplate restTemplate;
#Autowired
private A a;
#InjectMocks
private C c;
#Before
public void setup() {
initMocks(this);
}
#Test
public void test1() throws IOException {
a.testA()
}
}
Doesn't mock the RestTemplate object, it tries to hit the outside world. Any advice on how to resolve this?
Achieve this by using SpringRunner and #MockBean
#RunWith(SpringRunner.class) is used to provide a bridge between Spring Boot test features and JUnit. Whenever we are using any Spring Boot testing features in out JUnit tests, this annotation will be required.
The #SpringBootTest annotation can be used when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests.
Annotation that can be used to add mocks to a Spring ApplicationContext. Can be used as a class level annotation or on fields in either #Configuration classes, or test classes that are #RunWith the SpringRunner.
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyIt {
#MockBean
private RestTemplate restTemplate;
#Autowired
private A a;
#Before
public void setup() {
initMocks(this);
}
#Test
public void test1() throws IOException {
given(this.restTemplate.doSomethingInOutsideWorld()).willReturn(custom object);
a.testA()
}
}
my application normally works fine, but when I run tests, or build application by maven, application is shutting down due tests with errors java.lang.NullPointerException. I debugged it and find out my that my beans in service layer are not Autowired and they are null.
Here is my class with tests:
public class CompanyServiceSimpleTest {
private CompanyService companyService;
#Before
public void setUp() {
companyService = new CompanyServiceImpl();
}
// Here is sample test
#Test
public void testNumberOfCompanies() {
Assert.assertEquals(2, companyService.findAll().size());
}
}
companyService is initialized, but beans in it not. Here is CompanyServiceImpl:
#Service
public class CompanyServiceImpl implements CompanyService {
#Autowired
private CompanyRepository companyRepository; // is null
#Autowired
private NotificationService notificationService; // is null
#Override
public List<CompanyDto> findAll() {
List<CompanyEntity> entities = companyRepository.find(0, Integer.MAX_VALUE);
return entities.stream().map(Translations.COMPANY_DOMAIN_TO_DTO).collect(Collectors.toList());
}
// ... some other functions
}
So when is called companyRepository.find() applications crashes. Here is repository class:
#Repository
#Profile("inMemory")
public class CompanyInMemoryRepository implements CompanyRepository {
private final List<CompanyEntity> DATA = new ArrayList<>();
private AtomicLong idGenerator = new AtomicLong(3);
#Override
public List<CompanyEntity> find(int offset, int limit) {
return DATA.subList(offset, Math.min(offset+limit, DATA.size()));
}
// ... some others functions
}
I have set up profile for that service but I had that VM options in Idea:
-Dspring.profiles.active=develpment,inMemory
So it should works.
To make autowiring work it has to be a Spring integration test. You have to anotate your test class with:
#RunWith(SpringJUnit4ClassRunner.class) and #ContextConfiguration(classes = {MyApplicationConfig.class})
If it is a Spring Boot app e.g.:
#RunWith(SpringJUnit4ClassRunner.class) and #SpringBootTest(classes = {MyApp.class, MyApplicationConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
More on this topic: http://www.baeldung.com/integration-testing-in-spring and http://www.baeldung.com/spring-boot-testing
You are not configuring Spring in your TestClasses, so it's impossible to inject anything...
Try configurin your class with
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "path to your config xml" })
A little example:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:config/applicationContext.xml"})
public class MyTest {
#Autowired
private MyClass myInjectedClass;
#Test
public void someTest() {
assertNotNull(myInjectedClass);
}
}
I'm quite a newbie to Spring boot, but here's the problem I'm facing now:
// Application.java
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Autowired
private Cluster cluster = null;
#PostConstruct
private void migrateCassandra() {
Database database = new Database(this.cluster, "foo");
MigrationTask migration = new MigrationTask(database, new MigrationRepository());
migration.migrate();
}
}
So basically, I'm trying to bootstrap a spring application, and after that, do some cassandra migrations.
I also have defined a repository for my user model:
// UserRepo.java
public interface UserRepo extends CassandraRepository<User> {
}
Now I'm trying to test my repo class using the following simple test case:
// UserRepoTest.java
#RunWith(SpringRunner.class)
#AutoConfigureTestDatabase(replace = Replace.NONE)
#DataJpaTest
public class UserRepoTest {
#Autowired
private UserRepo userRepo = null;
#Autowired
private TestEntityManager entityManager = null;
#Test
public void findOne_whenUserExists_thenReturnUser() {
String id = UUID.randomUUID().toString();
User user = new User();
user.setId(id);
this.entityManager.persist(user);
assertEquals(this.userRepo.findOne(user.getId()).getId(), id);
}
#Test
public void findOne_whenUserNotExists_thenReturnNull() {
assertNull(this.userRepo.findOne(UUID.randomUUID().toString()));
}
}
I would expect the test to pass, but instead, I got an error saying "No qualifying bean of type 'com.datastax.driver.core.Cluster' available". It looks like spring failed to autowire the cluster object, but why is that? How do I fix this? Thanks a lot!
The test environment needs to know where your beans are defined, so you have to tell it the location.
In your test class, add the #ContextConfiguration annotation:
#RunWith(SpringRunner.class)
#AutoConfigureTestDatabase(replace = Replace.NONE)
#DataJpaTest
#ContextConfiguration(classes = {YourBeans.class, MoreOfYourBeans.class})
public class UserRepoTest {
#Autowired
private UserRepo userRepo = null;
#Autowired
private TestEntityManager entityManager = null;
I use SpringBeanAutowiringSupport for bean injection in some objects. Problem is, that injection of beans does not work in jUnit tests. For testing is used SpringJUnit4ClassRunner.
public class DossierReportItemXlsImporterImpl implements DossierRerportItemXlsImporer {
private final Logger logger = Logger.getLogger(getClass());
// are not autowired.
#Autowired
private DossierReportService dossierReportService;
#Autowired
private DossierReportItemService dossierReportItemService;
#Autowired
private NandoCodeService nandoCodeService;
public DossierReportItemXlsImporterImpl(){
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
//...
}
public class DossierRerportItemXlsImporerTest extends AuditorServiceTest{
// injected OK
#Autowired
private DossierReportService dossierReportService;
#Autowired
private DossierReportItemService dossierReportItemService;
#Test
public void testXlsImport(){
DossierRerportItemXlsImporer importer = new DossierReportItemXlsImporterImpl();
importer.processImport(createDossierReport(), loadFile());
// ...
}
// ...
}
Does anyone have any idea, why injection using SpringBeanAutowiringSupport does not work in jUnit tests?
well spring + junit team have already fixed this . look this link -- >
spring unit testing
otherwise you can call the spring context and use the getBean method , but in that way you can even do it with a simple main test inside your class instead of junit test
**note if you use the spring + junit config you have to put the test-spring-context.xml into the test package
Thanks to M. Denium's, his solution workds.
public class DossierReportItemXlsImporterImpl implements DossierRerportItemXlsImporer {
private final Logger logger = Logger.getLogger(getClass());
#Autowired
private DossierReportService dossierReportService;
#Autowired
private DossierReportItemService dossierReportItemService;
#Autowired
private NandoCodeService nandoCodeService;
public DossierReportItemXlsImporterImpl(final ApplicationContext contex){
contex.getAutowireCapableBeanFactory().autowireBean(this);
}
//...
}
public class DossierRerportItemXlsImporerTest extends AuditorServiceTest{
#Autowired
private ApplicationContext context;
#Autowired
private DossierReportService dossierReportService;
#Autowired
private DossierReportItemService dossierReportItemService;
#Test
public void testXlsImport(){
DossierRerportItemXlsImporer importer = new DossierReportItemXlsImporterImpl(context);
importer.processImport(createDossierReport(), loadFile());
// ...
}
// ...
}
I made my own version that supports passing in an ApplicationContext not just limited to WebApplicationContext. This will allow it to work in both test and normal context.
/**
* This is an implementation of {#link org.springframework.web.context.support.SpringBeanAutowiringSupport} that
* has a fallback that can be used in unit tests.
*/
public final class SpringBeanAutowiringSupport {
private static final ThreadLocal<ApplicationContext> applicationContextThreadLocal = new ThreadLocal<>();
private SpringBeanAutowiringSupport() {}
public static void setApplicationContext(final ApplicationContext applicationContext) {
applicationContextThreadLocal.set(applicationContext);
}
public static void processInjectionBasedOnCurrentContext(Object target) {
var cc = ContextLoader.getCurrentWebApplicationContext();
if (cc != null) {
org.springframework.web.context.support.SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(target);
} else if (applicationContextThreadLocal.get() != null) {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(applicationContextThreadLocal.get().getAutowireCapableBeanFactory());
bpp.processInjection(target);
}
}
public static void unload() {
applicationContextThreadLocal.remove();
}
}
To make it easier to use on tests, I add a TestExecutionListener
public class SpringBeanAutowiringSupportTestExecutionListener extends AbstractTestExecutionListener {
#Override
public void afterTestMethod(final TestContext testContext) {
SpringBeanAutowiringSupport.unload();
}
#Override
public void beforeTestMethod(final TestContext testContext) {
SpringBeanAutowiringSupport.setApplicationContext(testContext.getApplicationContext());
}
}
Then use it in my tests with
#RunWith(SpringRunner.class)
#TestExecutionListeners(listeners = {SpringBeanAutowiringSupportTestExecutionListener.class}, mergeMode = MERGE_WITH_DEFAULTS)
public class MyTest {
...
}