I am using Spring reactive and want transactional behavior in saving data into two different tables. I have used .as(operator::transactional). I am facing issue while doing Unit Testing
DAO Class Code with autowired Repositories & TransactionalOperator:
var transactionDetails = getTransactionDetails(txnDetailsRequest, accountDetailsRequest, httpReqHeaders);
var accountProcessingDetails = getAccountProcessingDetails(accountDetailsRequest);
var response = transactionDetailsRepository.save(transactionDetails)
.then(accountProcessingDetailsRepository.save(accountProcessingDetails))
.onErrorMap(DataAccessException.class, handleDataAccessException)
.onErrorMap(SocketTimeoutException.class, timeOutException)
.as(operator::transactional)
.then(Mono.just("Insertion Successful"));
Test case code:
#Test
public void test() {
TransactionDetails transactionDetails = TestHelper.createTransactionDetails();
when(transactionDetailsRepository.save(any(TransactionDetails.class)))
.thenReturn(Mono.just(transactionDetails));
var accountProcessingDetails = TestHelper.createAccountProcessingDetails();
when(accountProcessingDetailsRepository.save(any(AccountProcessingDetails.class)))
.thenReturn(Mono.just(accountProcessingDetails));
var txnEligibilityRequest = TestHelper.getTransactionsEligibilityRequest();
Map<String, String> headerMap = TestHelper.getHeaderMap();
var responseMono = transactionDetailsDAOImpl.saveTransactionDetails(txnEligibilityRequest.getDataRequest().getTxnDetailsRequestList().get(0),
txnEligibilityRequest.getDataRequest().getAccountDetailsRequest(), headerMap);
StepVerifier.create(responseMono).assertNext(response -> {
assertEquals(response, "Insertion Successful");
}).verifyComplete();
}
While running the test case I'm getting the following error, on the line .then(Mono.just("Insertion Successful"));:
java.lang.NullPointerException: Cannot invoke "reactor.core.publisher.Mono.then(reactor.core.publisher.Mono)" because the return value of "reactor.core.publisher.Mono.as(java.util.function.Function)" is null
Can someone help in this issue:
What's wrong in the current code?
How to mock the org.springframework.transaction.reactive.TransactionalOperator object?
Related
The goal of my springBoot webflux r2dbc application is Controller accepts a Request including a list of DB UPDATE or INSERT details, and Response a result summary back.
I can write a ReactiveCrudRepository based repository to implement each DB operation. But I don't know how to write the Service to group the executions of the list of DB operations and compose a result summary response.
I am new to java reactive programing. Thanks for any suggestions and help.
Chen
I get the hint from here: https://www.vinsguru.com/spring-webflux-aggregation/ . Ideas are :
From request to create 3 Monos
Mono<List> monoEndDateSet -- DB Row ids of update operation;
Mono<List> monoCreateList -- DB Row ids of new inserted;
Mono monoRespFilled -- partly fill some known fields;
use Mono.zip aggregate the 3 monos, map and aggregate the Tuple3 to Mono to return.
Below are key part of codes:
public Mono<ChangeSupplyResponse> ChangeSupplies(ChangeSupplyRequest csr){
ChangeSupplyResponse resp = ChangeSupplyResponse.builder().build();
resp.setEventType(csr.getEventType());
resp.setSupplyOperationId(csr.getSupplyOperationId());
resp.setTeamMemberId(csr.getTeamMemberId());
resp.setRequestTimeStamp(csr.getTimestamp());
resp.setProcessStart(OffsetDateTime.now());
resp.setUserId(csr.getUserId());
Mono<List<Long>> monoEndDateSet = getEndDateIdList(csr);
Mono<List<Long>> monoCreateList = getNewSupplyEntityList(csr);
Mono<ChangeSupplyResponse> monoRespFilled = Mono.just(resp);
return Mono.zip(monoRespFilled, monoEndDateSet, monoCreateList).map(this::combine).as(operator::transactional);
}
private ChangeSupplyResponse combine(Tuple3<ChangeSupplyResponse, List<Long>, List<Long>> tuple){
ChangeSupplyResponse resp = tuple.getT1().toBuilder().build();
List<Long> endDateIds = tuple.getT2();
resp.setEndDatedDemandStreamSupplyIds(endDateIds);
List<Long> newIds = tuple.getT3();
resp.setNewCreatedDemandStreamSupplyIds(newIds);
resp.setSuccess(true);
Duration span = Duration.between(resp.getProcessStart(), OffsetDateTime.now());
resp.setProcessDurationMillis(span.toMillis());
return resp;
}
private Mono<List<Long>> getNewSupplyEntityList(ChangeSupplyRequest csr) {
Flux<DemandStreamSupplyEntity> fluxNewCreated = Flux.empty();
for (SrmOperation so : csr.getOperations()) {
if (so.getType() == SrmOperationType.createSupply) {
DemandStreamSupplyEntity e = buildEntity(so, csr);
fluxNewCreated = fluxNewCreated.mergeWith(this.demandStreamSupplyRepository.save(e));
}
}
return fluxNewCreated.map(e -> e.getDemandStreamSupplyId()).collectList();
}
...
I wanted to update a nested list but I experience a strange behavior where I have to call method twice to get it done...
Here is my POJO:
#Document(collection = "company")
data class Company (
val id: ObjectId,
#Indexed(unique=true)
val name: String,
val customers: MutableList<Customer> = mutableListOf()
//other fields
)
Below is my function from custom repository to do the job which I based on this tutorial
override fun addCustomer(customer: Customer): Mono<Company> {
val query = Query(Criteria.where("employees.keycloakId").`is`(customer.createdBy))
val update = Update().addToSet("customers", customer)
val upsertOption = FindAndModifyOptions.options().upsert(true)
//if I uncomment below this will work...
//mongoTemplate.findAndModify(query, update, upsertOption, Company::class.java).block()
return mongoTemplate.findAndModify(query, update, upsertOption, Company::class.java)
}
In order to actually add this customer I have to either uncomment the block call above or call the method two times in the debugger while running integration tests which is quite confusing to me
Here is the failing test
#Test
fun addCustomer() {
//given
val company = fixture.company
val initialCustomerSize = company.customers.size
companyRepository.save(company).block()
val customerToAdd = CustomerReference(id = ObjectId.get(),
keycloakId = "dummy",
username = "customerName",
email = "email",
createdBy = company.employees[0].keycloakId)
//when, then
StepVerifier.create(companyCustomRepositoryImpl.addCustomer(customerToAdd))
.assertNext { updatedCompany -> assertThat(updatedCompany.customers).hasSize(initialCustomerSize + 1) }
.verifyComplete()
}
java.lang.AssertionError:
Expected size:<3> but was:<2> in:
I found out the issue.
By default mongo returns entity with state of before update. To override it I had to add:
val upsertOption = FindAndModifyOptions.options()
.returnNew(true)
.upsert(true)
READ EDIT
I have a similar implementation to AsyncCrudAppService related to filtering queries. When I run tests on top of ABPs implementation of Application Services derived of AsyncCrudAppServiceBase, everything runs fine. When I do the same running on top of my custom "filters", I get the following error:
System.ObjectDisposedException : Cannot access a disposed object [...]
Object name: 'DataManagerDbContext'.
I know the solution is using IUnitOfWorkManager and calling Begin() method to define a UnitOfWork, but since I am working with AppServices, I thought there was already a UnitOfWork defined. These are my methods:
public PagedResultDto<StateDetails> GetEditorList(EditorRequestDto input)
{
var query = _stateRepository.GetAllIncluding(p => p.Country).AsQueryable();
query = ApplySupervisorFilter(query);
query = query.ApplyFiltering(input, "Name");
var totalCount = query.Count();
query = query.ApplySorting<State, int, PagedAndSortedResultRequestDto>(input);
query = query.ApplyPaging<State, int, PagedAndSortedResultRequestDto>(input);
var entities = query.ToList();
return new PagedResultDto<StateDetails>(totalCount, ObjectMapper.Map<List<StateDetails>>(entities));
}
private IQueryable<State> ApplySupervisorFilter(IQueryable<State> query)
{
if (!SettingManager.GetSettingValue<bool>(AppSettingNames.SupervisorFlag))
{
query = ApplyUncategorizedFilter(query);
}
return query;
}
private IQueryable<State> ApplyUncategorizedFilter(IQueryable<State> query)
{
return query.Where(
p => !p.CountryId.HasValue);
}
My passing test (with manual UnitOfWork):
[Fact]
public async Task GetEditorListWithouSupervisorFlag_Test()
{
using (UnitOfWorkManager.Begin())
{
await ChangeSupervisorFlag(false);
var result = _stateAppService.GetEditorList(
new EditorRequestDto
{
MaxResultCount = 10,
});
result.Items.Any(p => p.Country == null).ShouldBe(true);
}
}
Does anybody know an solution to this "issue"? It would be annoying to define a UnitOfWork for every test I perform. It also seems like I am doing something wrong
EDIT
I have solved the issue. I must use an interface for my Application Service when running tests so it is able to mock it properly
I have solved the issue. I must use an interface for my Application Service when running tests so it is able to mock it properly
I'm trying to bring test layer to my project but I'm not getting there :( hope someone can help me.
Controller (based on Automapper mapping and Dependency Injection Container):
public virtual ActionResult SearchCategories(string keywords)
{
var result = _categoryService.SearchCategories(keywords);
var resultViewModel = Mapper.
Map<IList<SearchCategoriesDto>,
IList<SearchCategoriesViewModel>>(result);
return View(resultViewModel);
}
Service Layer:
public IList<SearchCategoriesDto> SearchCategories(String keywords)
{
// Find the keywords in the Keywords table
var keywordQuery = _keywordRepository.Query;
foreach (string keyword in splitKeywords)
{
keywordQuery = keywordQuery.Where(p => p.Name == keyword);
}
// Get the Categories from the Search
var keywordAdCategoryQuery = _keywordAdCategoryRepository.Query;
var categoryQuery = _categoryRepository.Query;
var query = from k in keywordQuery
join kac in keywordAdCategoryQuery on k.Id equals kac.Keyword_Id
join c in categoryQuery on kac.Category_Id equals c.Id
select new SearchCategoriesDto
{
Id = c.Id,
Name = c.Name,
SearchCount = keywordAdCategoryQuery
.Where(s => s.Category_Id == c.Id)
.GroupBy(p => p.Ad_Id).Count(),
ListController = c.ListController,
ListAction = c.ListAction
};
var searchResults = query.Distinct().ToList();
return searchResults;
}
Test maded but not working:
[TestMethod]
public void Home_SearchCategories_Test()
{
// Setup
var catetoryService = new CategoryService(
_categoryRepository,
_keywordRepository,
_keywordAdCategoryRepository);
// Act
var result = catetoryService.SearchCategories("audi");
// Add verifications here
Assert.IsTrue(result.Count > 0);
}
Thanks.
I am assuming you want to create an integration test for your category service, using real repositories and database. (as oposed to an unit test where you would use stub\mocks for those repositories and test the service class in isolation)
So you would have a seaparated test assembly where you will add your integration tests, for example having a class for the integration tests at the service level.
On that class you will then create instances of all the repositories and the CategoryService before running each test, on a method with the attribute [TestInitialize]. (A method with this attribute will be run by msTest before each test)
As you are also truly working with the database you would like to be sure that any resources used are disposed. For example, disposing an entity framework context. In that case you would add a method with an attribute [TestCleanup] where you will perform any cleanup logic needed. (A method with this attribute will be run by msTest after each test)
With those methods you will make sure you have a fresh service and repositories instances on each test. Then you will implement each individual integration test following the AAA pattern (Arrange, Act, Assert)
So an example of your integration test class with that single test may look like:
public class CategoryServiceIntegrationTest
{
//dependencies of your class under test
private ICategoryRepository _categoryRepository;
private IKeywordRepository _keywordRepository;
private IKeywordAdCategoryRepository _keywordAdCategoryRepository;
//your class under test
private CategoryService _categoryService;
[TestInitialize]
public void InitializeBeforeRunningATest()
{
//manually create instances of the classes implementing the repositories
//I donĀ“t know about how they are implemented but I guess
//you would need to provide the name of a connection string in the config file (in that case this should be in a config file of the test project)
//,the connection string itself
//or maybe you need to initialize an entity framework context
_categoryRepository = new CategoryRepository( /*whatever you need to provide*/);
_keywordRepository = new KeywordRepository( /*whatever you need to provide*/);
_keywordAdCategoryRepository = new KeywordAdCategoryRepository( /*whatever you need to provide*/);
//Create the class under test with all repositories dependencies
//as it is an integration test, they are your real objects and not mocks\stubs
_categoryService = new CategoryService(_categoryRepository,
_keywordRepository,
_keywordAdCategoryRepository);
}
[TestCleanup]
public void CleanDatabaseResources()
{
//just in case you need to do something like disposing an EF context object
}
[TestMethod]
public void Home_SearchCategories_Test()
{
// Arrange
var keywords = "audi";
// Act (the _categoryService instance was created in the initialize method)
var result = _categoryService.SearchCategories(keywords);
// Assert
Assert.IsTrue(result.Count > 0);
}
}
Solution to build an Integration test for a Service (in this case, Category Service), using Autofac, Automapper (not necessary in this Service but if it would be necessary, you would need to put in the TestInitialize method as you can see in the coment line in the following solution) and Entity Framework with Daniel J.G. help (thanks Daniel):
First of all I created a separated Test Project using MSTest (only because there is a lot of documentation about it).
Second you need to put the connection string for the Entity Framework where the test data is:
<connectionStrings>
<add name="DB" connectionString="Data Source=.\sqlexpress;Database=DBNAME;UID=DBUSER;pwd=DBPASSWORD;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
In the < configuration > section after the < / configSections >
Third you create the class for the test:
namespace Heelp.Tests
{
[TestClass]
public class CategoryServiceIntegrationTest
{
// Respositories dependencies
private IRepository<Category> _categoryRepository;
private IRepository<Keyword> _keywordRepository;
private IRepository<KeywordAdCategory> _keywordAdCategoryRepository;
// Service under test: Category Service
private CategoryService _categoryService;
// Context under test: HeelpDB Connection String in app.config
private HeelpDbContext db;
[TestInitialize]
public void InitializeBeforeRunningATest()
{
// IoC dependencies registrations
AutofacConfig.RegisterDependencies();
// HERE YOU CAN CALL THE AUTOMAPPER CONFIGURE METHOD
// IN MY PROJECT I USE AutoMapperConfiguration.Configure();
// IT'S LOCATED IN THE App_Start FOLDER IN THE AutoMapperConfig.cs CLASS
// CALLED FROM GLOBAL.ASAX Application_Start() METHOD
// Database context initialization
db = new HeelpDbContext();
// Repositories initialization
_categoryRepository = new Repository<Category>(db);
_keywordRepository = new Repository<Keyword>(db);
_keywordAdCategoryRepository = new Repository<KeywordAdCategory>(db);
// Service initialization
_categoryService = new CategoryService(_categoryRepository,
_keywordRepository,
_keywordAdCategoryRepository);
}
[TestCleanup]
public void CleanDatabaseResources()
{
// Release the Entity Framework Context for other tests that will create a fresh new context.
// With this method, we will make sure that we have a fresh service and repositories instances on each test.
db.Dispose();
}
[TestMethod]
public void Home_SearchCategories_Test()
{
// Arrange
var keywords = "audi";
// Act (the _categoryService instance was created in the initialize method)
var result = _categoryService.SearchCategories(keywords);
// Assert
Assert.IsTrue(result.Count > 0);
}
}
}
Now you just have to run the test to see if it passes.
To garantee integration tests, I would recomend a second database identical from the original/production database in terms of tables, but with only your test data.
This will ensure that the tests results will remain the same based on your test data.
The only drawback is that you will need to keep sincronized the tables, but you can use SQL Admin Studio Freeware Tool from Simego to achieve that.
Regards.
I am working on Unit testing the MVC3 site using NUnit, MvcContrib.TestHelper package but I am facing the exception "Object reference not set to an instance of an object" when my test method accesses the controller having TryUpdateModel. I dont know what to do to pass the test. please help me in this.
I am also giving a code for that :
Action from Controller which test method calls, is given below :
public JsonResult AddPatient()
{
bool returnStatus;
string returnErrorMessage;
List<string> returnMessage;
PatientBLL patientBLL = new PatientBLL();
Models.PatientViewModel patientViewModel = new Models.PatientViewModel();
TryUpdateModel(patientViewModel);
Patient patient = patientBLL.AddPatient(
patientViewModel,
out returnMessage,
out returnStatus,
out returnErrorMessage);
patientViewModel.UpdateViewModel(patient, typeof(Patient).GetProperties());
patientViewModel.ReturnMessage = returnMessage;
patientViewModel.ReturnStatus = returnStatus;
return Json(patientViewModel);
}
and the test method which calls the above action is given below :
[Test]
public void Test_AddPatient()
{
TestControllerBuilder builder = new TestControllerBuilder();
string uniquePatientKey = GenerateUniqueID();
builder.Form["MedicalID"] = uniquePatientKey;
builder.Form["SocialSecurityNumber"] = uniquePatientKey;
builder.Form["FirstName"] = "Khushi";
builder.Form["LastName"] = "Maahi";
builder.Form["AddressLine1"] = "ABCD";
builder.Form["AddressLine2"] = "Technologies";
builder.Form["City"] = "OOna";
builder.Form["State"] = "UP";
builder.Form["ZipCode"] = "98456-7329";
builder.Form["PhoneNumber"] = "(425)882-8080";
builder.Form["DateOfBirth"] = "10/28/1987";
builder.Form["PatientDateOfBirth"] = "10/28/1987";
builder.Form["EffectiveDate"] = "01/01/1995";
builder.Form["PatientEffectiveDate"] = "01/01/1995";
PatientController patientController = builder.CreateController<PatientController>();
JsonResult jsonResult = (JsonResult)patientController.AddPatient();
dynamic jsonData = jsonResult.Data;
string jsonMessage=Convert.ToString(jsonData.ReturnMessage);
Assert.AreEqual(jsonData.ReturnStatus, true );
Assert.Greater(jsonData.PatientID, 0);
}
Please give me the solution for my probelm.
You have a null somewhere in patientViewModel. Can you post that type here? This could be a problem with mixing models and view models.