I have a spring-boot application.
I have entity:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Document(COLLECTION_NAME)
public class PersonEntity {
public static final String COLLECTION_NAME = "person_info";
private static final String PERSON_NAME = "person_name";
#Id
private PersonId id;
#Field(name = PERSON_NAME)
private String personName;
#Indexed(name = "ttl_index", expireAfterSeconds=20)
private LocalDateTime date;
}
I have a repository interface:
public interface PersonRepository {
void saveWithTtl(PersonEntity entity);
}
The repository implementation:
#Slf4j
#Repository
public class PersonRepositoryImpl implements PersonRepository {
private final int expireAfterSeconds;
private final ReactiveMongoTemplate mongoTemplate;
public PersonRepositoryImpl(#Value("${ttl.index}") int expireAfterSeconds,
ReactiveMongoTemplate mongoTemplate) {
this.expireAfterSeconds = expireAfterSeconds;
this.mongoTemplate = mongoTemplate;
}
#Override
public void saveWithTtl(PersonEntity entity) {
mongoTemplate.indexOps(PersonEntity.class)
.ensureIndex(new Index().on(PersonEntity.CREATED_AT, ASC)
.expire(expireAfterSeconds)).subscribe(result -> log.info("Ttl index has been created: {}", result));
mongoTemplate.save(entity).subscribe(result -> log.info("Entity has been saved: {}", result));
}
}
And, finally, I have test that does not work:
#DataMongoTest
#Testcontainers
public class PersonRepositoryIT {
#Autowired
private ReactiveMongoTemplate mongoTemplate;
#Autowired
private PersonRepository repository;
#Container
private static MongoDbContainer mongoDbContainer = new MongoDbContainer();
#AfterEach
void cleanUp() {
repository.deleteAll();
}
#DynamicPropertySource
static void registerMongoProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", mongoDbContainer::getReplicaSetUrl);
}
#Test
public void shouldCreateAndDeleteRecordsAfterDelay_whenSaveWithTtl_givenDefinedTll() {
//given
PersonEntity givenEntity = PersonEntity.builder().createdAt(LocalDateTime.now())
.personName("Joe")
.id(PERSON_ID).build();
//when
repository.saveWithTtl(givenEntity);
//then
StepVerifier.create(mongoTemplate.estimatedCount(PersonEntity.COLLECTION_NAME))
.expectNext(1L)
.verifyComplete();
}
}
On expectNext it fails coz it returns 0 and not 1.
mongoTemplate.estimatedCount returns 0
When I test the repository from Postman (repo is calling inside service), it creates the document in MongoDB wil ttl index, as expected.
In test fonfig I have set the ${ttl.index} to 20.
What am I doing wrong?
I don't know if it is to late, but I had the same problem today.
I Found your question looking for an answer for my problem hahahaha.
This snipped worked for me:
#Container
public static MongoDBContainer container = new MongoDBContainer(DockerImageName.parse("mongo:6"));
#DynamicPropertySource
static void mongoDbProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", container::getReplicaSetUrl);
}
#Bean
public ReactiveMongoTemplate reactiveMongoTemplate() throws Exception {
container.start();
ConnectionString connectionString = new ConnectionString(container.getReplicaSetUrl());
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
MongoClient mongoClient = MongoClients.create(mongoClientSettings);
return new ReactiveMongoTemplate(mongoClient,"test");
}
Apparently ReactiveMongoTemplate is not being injected by default, then I created my own Bean an it worked
Related
I'm trying to do a simple get query on springboot using mongodb as database engine
I have tried with several stuff(sending the data as ObjectId and even changing the repository)
public ResponseEntity<Track> get(String trackId) {
Track find = mongoTemplate.findById(new ObjectId(trackId), Track.class);
Optional<Track> track = tracksRepository.findById(trackId);
if (track.isPresent()) {
return new ResponseEntity<>(track.get(), HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
with mongo config
#Configuration
#EnableMongoRepositories(basePackages = "data.store.repositories")
public class MongoConfig extends AbstractMongoClientConfiguration {
private final Logger LOGGER = Logger.getLogger(this.getClass().getSimpleName());
#Primary
#Bean
#Override
public MongoClient mongoClient() {
return MongoClients.create(MongoClientSettings.builder()
.applyToClusterSettings(builder -> builder.hosts(Arrays.asList(new ServerAddress(host, port))))
.build());
}
private MongoCredential mongoCredentials() {
return MongoCredential.createCredential(username, database, password.toCharArray());
}
#Bean
public MongoTemplate mongoTemplate() {
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), getDatabaseName());
mongoTemplate.setReadPreference(ReadPreference.secondaryPreferred());
return mongoTemplate;
}
protected String getDatabaseName() {
return database;
}
#Override
public boolean autoIndexCreation() {
return false;
}
}
EDIT: Adding class for context
#Document("track")
public class Track {
#Id
#Field(ATTR_ID)
#JsonProperty(ATTR_ID)
public String id;
public static final String ATTR_ID = "id";
}
and getting always null, with existing keys on my database. could you help me find the issue?
Thanks in advance
I tried this with similar configuration class and found the following worked fine creating/accessing data using MongoTemplate.
The POJO class:
public class Test {
#MongoId(FieldType.OBJECT_ID)
private String id;
private String name;
public Test() {
}
public Test(String s) {
super();
this.name = s;
}
// get, set methods
public String toString( ) {
return id + " - " + name;
}
}
From Spring's CommandLineRunner.run():
// Insert a document into the database
Test t1 = new Test("alpha");
t1 = mt.insert(t1);
System.out.println(t1); // 61e7de9f5aadc2077d9f4a58 - alpha
// Query from the database using the _id
ObjectId id = new ObjectId("61e7de9f5aadc2077d9f4a58");
Test t2 = mt.findById(id, Test.class);
System.out.println(t2);
Note that you need to do this from the class where you are running the code:
#Autowired private MongoTemplate mt;
You can use the #MongoId or #Id annotations in our POJO class to represent MongoDB _id field. The type of the field can be a String or ObjectId. It depends upon how you define.
See this from Spring Data MongoDB documentation on How the _id Field is Handled in the Mapping Layer using:
#MongoId
#Id
Solution is to add to MongoId annotation field type object id
#MongoId(FieldType.OBJECT_ID)
private String id;
How to write unit test (via Junit) for MongoTemplate updateFirst() method ?
I have this service class:
#Service
public class TemplateServiceImpl implements TemplateService
{
#Resource
private TemplateRepository templateRepository;
#Resource
private MongoTemplate mongoTemplate;
#Override
public TemplateDto create(TemplateDto templateDto)
{
TemplateDto result;
templateDto.setDeleted(false);
result = templateRepository.insert(templateDto);
return result;
}
#Override
public boolean update(ObjectId id, TemplateDto templateDto)
{
if (templateDto != null)
{
Update update = new Update();
if (templateDto.getName() != null)
{
update.set("name", templateDto.getName());
}
if (templateDto.getTemplate() != null)
{
update.set("template", templateDto.getTemplate());
}
if (templateDto.isDeleted())
{
update.set("deleted", Boolean.TRUE);
}
update.set("lastUpdate", new Date());
return mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(id)), update, "template")
.wasAcknowledged();
}
return false;
}
}
I want write unit test for update method . This is my relevant test class:
#ExtendWith(MockitoExtension.class)
class TemplateServiceImplTest
{
#InjectMocks
private static TemplateServiceImpl templateService;
#Mock
private static TemplateRepository templateRepository;
#Mock
private static MongoTemplate mongoTemplate;
private static TemplateDto templateDto;
private static List<TemplateDto> listOfTemplates;
private static String requestBody;
#BeforeAll
private static void setUp(){
templateDto = createTemplateDto();
listOfTemplates = createListOfTemplate();
requestBody = createRequesyBody();
}
#Test
void create()
{
when(templateRepository.insert(templateDto)).thenReturn(templateDto);
TemplateDto actualResult = templateService.create(TemplateServiceImplTest.templateDto);
assertThat(actualResult).isNotNull();
assertThat(actualResult).isInstanceOf(TemplateDto.class);
assertThat(actualResult.getId()).isEqualTo(templateDto.getId());
}
#Test
void update()
{
Update update = new Update();
/*
when(mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(templateDto.getId().toString())) ,update , TemplateDto.class)).thenReturn(
UpdateResult.acknowledged());
*/
/*
given(mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(templateDto.getId().toString())) ,update , TemplateDto.class));
*/
boolean actualResult = templateService.update(templateDto.getId(), templateDto);
assertThat(actualResult).isNotNull();
assertThat(actualResult).isTrue();
}
// other methods...
}
When I run test I get NPE for mongoTemplate at update method , I searched and I saw this 2 approach:
when(mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(templateDto.getId().toString())) ,update , TemplateDto.class)).thenReturn(
UpdateResult.acknowledged());
Or
given(mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(templateDto.getId().toString())) ,update , TemplateDto.class));
But none of them worked .
actually how to test MongoTemplate updateFirst method?
Thanks
How to write unit test (via Junit) for MongoTemplate updateFirst() method ?
I have this service class:
#Service
public class TemplateServiceImpl implements TemplateService
{
#Resource
private TemplateRepository templateRepository;
#Resource
private MongoTemplate mongoTemplate;
#Override
public TemplateDto create(TemplateDto templateDto)
{
TemplateDto result;
templateDto.setDeleted(false);
result = templateRepository.insert(templateDto);
return result;
}
#Override
public boolean update(ObjectId id, TemplateDto templateDto)
{
if (templateDto != null)
{
Update update = new Update();
if (templateDto.getName() != null)
{
update.set("name", templateDto.getName());
}
if (templateDto.getTemplate() != null)
{
update.set("template", templateDto.getTemplate());
}
if (templateDto.isDeleted())
{
update.set("deleted", Boolean.TRUE);
}
update.set("lastUpdate", new Date());
return mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(id)), update, "template")
.wasAcknowledged();
}
return false;
}
}
I want write unit test for update method .
This is my relevant test class:
#ExtendWith(MockitoExtension.class)
class TemplateServiceImplTest
{
#InjectMocks
private static TemplateServiceImpl templateService;
#Mock
private static TemplateRepository templateRepository;
#Mock
private static MongoTemplate mongoTemplate;
private static TemplateDto templateDto;
private static List<TemplateDto> listOfTemplates;
private static String requestBody;
#BeforeAll
private static void setUp(){
templateDto = createTemplateDto();
listOfTemplates = createListOfTemplate();
requestBody = createRequesyBody();
}
#Test
void create()
{
when(templateRepository.insert(templateDto)).thenReturn(templateDto);
TemplateDto actualResult = templateService.create(TemplateServiceImplTest.templateDto);
assertThat(actualResult).isNotNull();
assertThat(actualResult).isInstanceOf(TemplateDto.class);
assertThat(actualResult.getId()).isEqualTo(templateDto.getId());
}
#Test
void update()
{
Update update = new Update();
/*
when(mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(templateDto.getId().toString())) ,update , TemplateDto.class)).thenReturn(
UpdateResult.acknowledged());
*/
/*
given(mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(templateDto.getId().toString())) ,update , TemplateDto.class));
*/
boolean actualResult = templateService.update(templateDto.getId(), templateDto);
assertThat(actualResult).isNotNull();
assertThat(actualResult).isTrue();
}
// other methods...
}
I mocked MongoTemplate like this:
#Mock
private static MongoTemplate mongoTemplate;
when I run test I get NPE for mongoTemplate at update method , I searched and I saw this 2 approach:
when(mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(templateDto.getId().toString())) ,update , TemplateDto.class)).thenReturn(
UpdateResult.acknowledged());
// Or
given(mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(templateDto.getId().toString())) ,update , TemplateDto.class));
But None of them worked! .
Thank you all .
Note: I already look at and tried some approaches on SO e.g. How to test Spring's declarative caching support on Spring Data repositories?, but as most of them old, I cannot make them work properly and I need a solution with the latest library versions. So, I would be appreciated if you have a look at the question and help.
#Service
#EnableCaching
#RequiredArgsConstructor
public class DemoServiceImpl implements DemoService {
private static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
private final LabelTranslatableRepository translatableRepository;
private final LanguageService languageService;
#Override
public LabelDTO findByUuid(UUID uuid) {
final Label label = labelRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
final List<LabelTranslatable> translatableList = translatableRepository.findAllByEntityUuid(uuid);
return new LabelDTO(Pair.of(label.getUuid(), label.getKey()), translatableList);
}
}
I created the following Unit Test to test caching for the nethod above:
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
#ExtendWith(MockitoExtension.class)
class TextLabelServiceImpl_deneme_Test {
#Autowired
private CacheManager cacheManager;
#InjectMocks
private LabelService labelService;
#Mock
private LabelRepository labelRepository;
#Mock
private LabelTranslatableRepository translatableRepository;
#Test
void test_Cache() {
UUID uuid = UUID.randomUUID();
final TextLabel textLabel = new TextLabel();
textLabel.setId(1);
textLabel.setKey("key1");
TextLabelTranslatable textLabelTranslatable = new TextLabelTranslatable();
textLabelTranslatable.setEntityUuid(uuid);
textLabelTranslatable.setLanguage(SupportedLanguage.fr);
textLabelTranslatable.setValue("value1");
final List<TextLabelTranslatable> translatableList = new ArrayList<>();
translatableList.add(textLabelTranslatable);
when(labelRepository.findByUuid(uuid)).thenReturn(Optional.of(textLabel));
when(translatableRepository.findAllByEntityUuid(uuid)).thenReturn(translatableList);
TextLabelDTO result1 = labelService.findByUuid(uuid);
TextLabelDTO result2 = labelService.findByUuid(uuid);
assertEquals(result1, result2);
Mockito.verify(translatableRepository, Mockito.times(1)).findAllByEntityUuid(uuid);
}
I am not sure if there is a missing part in my test, but at the last line (Mockito.verify()), it returns 2 instead of 1 that means caching not works. But it is working properly and there is a problem in my test I think. How should I complete the unit test to check the caching properly?
You need to annotate the service class method with #Cacheable. Try to follow the code in this tutorial. The following test code works as expected
#Import({CacheConfig.class, DemoServiceImpl.class})
#ExtendWith(SpringExtension.class)
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
class DemoServiceImplTest {
#MockBean
private LabelRepository labelRepository;
#Autowired
private DemoServiceImpl demoService;
#Autowired
private CacheManager cacheManager;
#TestConfiguration
static class EmbeddedRedisConfiguration {
private final RedisServer redisServer;
public EmbeddedRedisConfiguration() {
this.redisServer = new RedisServer();
}
#PostConstruct
public void startRedis() {
redisServer.start();
}
#PreDestroy
public void stopRedis() {
this.redisServer.stop();
}
}
#Test
void givenRedisCaching_whenFindItemById_thenItemReturnedFromCache() {
UUID id = UUID.randomUUID();
Label aLabel = new Label(id, "label");
Mockito.when(labelRepository.findById(id)).thenReturn(Optional.of(aLabel));
Label labelCacheMiss = demoService.findByUuid(id);
Label labelCacheHit = demoService.findByUuid(id);
Mockito.verify(labelRepository, Mockito.times(1)).findById(id);
}
}
With this service class code:
#Service
#RequiredArgsConstructor
#EnableCaching
public class DemoServiceImpl {
public static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
#Cacheable(value = CACHE_NAME)
public Label findByUuid(UUID uuid) {
return labelRepository.findById(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
}
}
And this CacheConfig:
#Configuration
public class CacheConfig {
#Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration(DemoServiceImpl.CACHE_NAME,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));
}
#Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
}
}
Tried to create a configuration class which depends on another bean class using #DependsOn annotation and later found that #DependsOn only work along with #Bean and #Component and not with #Configuration.
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
private static final String URL_MAPPINGS = "/*";
private static final Class<?>[] classes = new Class[]{Application.class, Config1.class};
public static void main(String[] args) {
SpringApplication.run(classes, args);
}
...
}
#Configuration
public class MongoConfig {
#Value("${db.name}")
private String dbName;
#Value("${db.url}")
private String url;
#Override
public MongoClient mongo() {
ConnectionString connectionString = new ConnectionString("mongodb:"+url);
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), dbName);
}
}
#Import({ MongoConfig.class, Config2.class})
#Configuration
public class Config1 {
public BeanA beanA(MongoTemplate mongoTemplate, Environment environment) {
return new BeanA(mongoTemplate);
}
}
#Configuration
#ComponentScan("com.test")
//this should be processed after BeanA is created, similar to #DependsOn
public class Config2 {
...
}
public class BeanA {
private MongoTemplate mongoTemplate;
public BeanA(MongoTemplate mongoTemplate, Environment environment) {
this.mongoTemplate = mongoTemplate;
this.applicationPropertySource = new ApplicationPropertySource(SOURCE_NAME);
MutablePropertySources mutablePropertySources = ((StandardEnvironment) environment).getPropertySources();
mutablePropertySources.addFirst(applicationPropertySource);
getExistingProperties().forEach((key, val) -> applicationPropertySource.set(key, val));
}
private Map<String, String> getExistingProperties() {
//assume values are obtained from DB
}
public static class ApplicationPropertySource extends PropertiesPropertySource {
public ApplicationPropertySource(String name) {
super(name, new HashMap());
}
public Object set(String key, String value) {
return getSource().put(key, value);
}
}
I like to achieve this to avoid adding #DependsOn annotation on all the beans in the project, is this even possible?
Spring version is v1.5.12.RELEASE