Unit test EAP Asynchronus WebSerivice call in C# , Moq - c#

Hi I`m trying to unit test method with asynchronous web service call (asmx) . Code is as below. Problem is with mocking somehow, TaskCompletionSource .Should I use this pattern ?? Is there any way make it testable.
public async Task<Result> CreateConfAsync(byte[] sesja, Conference conference)
{
Result rez = new Result(-1,"error");
try
{
var confsStrXml = ConferenceHelper.createConfsXmlString(conference);
var tcs = new TaskCompletionSource<WsWynik>();
_proxy.KonferencjaZapiszCompleted += GetCreateConfAsyncCallBack;
_proxy.KonferencjaZapiszAsync(sesja, confsStrXml,tcs);
var wsWynik = await tcs.Task;
rez.status = wsWynik.status;
rez.message = wsWynik.status_opis;
if (rez.status != 0) SesjaExceptionCheck.SesjaCheckThrowIfError(rez.status, rez.message);
}
catch (Exception ex)
{
throw ex;
}
finally
{
_proxy.KonferencjaZapiszCompleted -= GetCreateConfAsyncCallBack;
}
return rez;
}
public void GetCreateConfAsyncCallBack(object sender, KonferencjaZapiszCompletedEventArgs e)
{
var tcs = (TaskCompletionSource<WsWynik>)e.UserState;
if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
tcs.TrySetResult(e.Result);
}
}
I`ve try to mock TaskCompletionSource, but no result .
[TestMethod]
public async Task CreateConf_ShouldBeOk()
{
var conf = new Mock<Conference>();
var tcs = new TaskCompletionSource<WsWynik>();
tcs.SetResult(default(WsWynik));
_proxy.Setup(x => x.KonferencjaZapiszAsync(_sesja, It.IsAny<string>(),tcs))
.Raises(mock => mock.KoszykKonferencjaZapiszCompleted += null, new EventArgs());
ConferenceRepository confRep = new ConferenceRepository(_proxy.Object, _dictRep.Object);
var res = await confRep.CreateConfAsync(_sesja, conf.Object);
Assert.IsTrue(1 == 1);
}

A few things need to be addressed.
The task completion source cannot be mocked as it is created within the method under test. It is not needed to mock anyway.
Use argument matchers for the mocked proxy method so that is will invoke when passed the values from the method.
For the event being raised, the proper event argument needs to be passed with the mock in order for the system under test to behave as desired.
[TestMethod]
public async Task CreateConf_ShouldBeOk() {
//Arrange
var conf = new Mock<Conference>();
var eventArgs = new KonferencjaZapiszCompletedEventArgs(...) {
Result = //...,
//populate the necessary properties
};
_proxy
.Setup(_ => _.KonferencjaZapiszAsync(
It.IsAny<byte[]>(),
It.IsAny<string>(),
It.IsAny<TaskCompletionSource<WsWynik>>()
))
.Raises(_ => _.KoszykKonferencjaZapiszCompleted += null, eventArgs);
var repository = new ConferenceRepository(_proxy.Object, _dictRep.Object);
//Act
var result = await repository.CreateConfAsync(_sesja, conf.Object);
//Assert
Assert.IsNotNull(result);
Assert.AreEqual(result.status, expectedStatus);
Assert.AreEqual(result.message , expectedMessage);
//assert the expected values of the result members
}

Related

xUnit and .Net core post test case issue with automapper(I guess)

I'm working on CRUD unit test cases with having configuration .Net 5, Automapper, xUnit etc.
The issue:
So right now I'm having issue specifically in post call and when I uses Dto.
I tried lots of ways to resole it and I was also able to resolve it if I don't use Dto in post call as input parameter and just use Entity it self. (But I don't want to expose entity so I want to make it working with Dto only)
Below is test which is not working and it's controller side implementation.
Failing Test case:
[Fact]
public void PostTest()
{
try
{
//Arrange
var surveyRequest = new SurveyRequest()
{
Id = 0,
Name = "Survey Request 1",
CreateDate = DateTime.Now,
CreatedBy = 1
};
var addedSurveyRequest = new SurveyRequest()
{
Id = 1,
Name = "Survey Request 1",
CreateDate = DateTime.Now,
CreatedBy = 1
};
//setup mock
Mock<IRepositoryWrapper> mockRepo = new Mock<IRepositoryWrapper>();
mockRepo.Setup(m => m.SurveyRequest.Add(addedSurveyRequest)).Returns(new Response<SurveyRequest>(true, addedSurveyRequest));
//auto mapper
var mockMapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new AutoMapperProfile());
});
var mapper = mockMapper.CreateMapper();
SurveyRequestController controller = new SurveyRequestController(repositories: mockRepo.Object, mapper: mapper);
//Act
var model = mapper.Map<SurveyRequest, SurveyRequestDto>(source: addedSurveyRequest);
var result = controller.Post(model); // The issue with this post call here is that response remains null on repository level.
//Assert
var okResult = result as OkObjectResult;
Assert.NotNull(okResult);
//we will make sure that returned object is dto and not actual entity
var response = okResult.Value as SurveyRequestDtoWithId;
Assert.NotNull(response);
Assert.Equal(expected: response.Name, actual: model.Name);
}
catch (Exception ex)
{
//Assert
Assert.False(true, ex.Message);
}
}
Controller side post call:
[HttpPost("Insert")]
public IActionResult Post([FromBody] SurveyRequestDto model)
{
try
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
//If I remove this mapping from here, test case will work. (see next working test case)
var entity = _mapper.Map<SurveyRequestDto, SurveyRequest>(source: model);
entity.CreateDate = System.DateTime.Now;
entity.CreatedBy = 1;
var response = _repositories.SurveyRequest.Add(entity: entity); //Response remains null here
_repositories.Save();
if (response.IsSuccess == true)
return new OkObjectResult(_mapper.Map<SurveyRequest, SurveyRequestDtoWithId>(source: response.Data));
else
return new ObjectResult(response.ErrorMessage) { StatusCode = 500 };
}
catch (Exception ex)
{
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
Working Test case:
[Fact]
public void PostTest2()
{
try
{
//Arrange
var surveyRequest = new SurveyRequest()
{
Id = 0,
Name = "Survey Request 1",
CreateDate = DateTime.Now,
CreatedBy = 1
};
var addedSurveyRequest = new SurveyRequest()
{
Id = 1,
Name = "Survey Request 1",
CreateDate = DateTime.Now,
CreatedBy = 1
};
//setup mock
Mock<IRepositoryWrapper> mockRepo = new Mock<IRepositoryWrapper>();
mockRepo.Setup(m => m.SurveyRequest.Add(surveyRequest)).Returns(value: new Response<SurveyRequest>(true, addedSurveyRequest));
//auto mapper
var mockMapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new AutoMapperProfile());
});
var mapper = mockMapper.CreateMapper();
//setup controlller
SurveyRequestController controller = new SurveyRequestController(repositories: mockRepo.Object, mapper: mapper);
//Act
//var model = mapper.Map<SurveyRequest, SurveyRequestDto>(source: surveyRequest);
var result = controller.Post2(entity: surveyRequest);
//Assert
var okResult = result as OkObjectResult;
Assert.NotNull(okResult);
///we will make sure that returned object is dto and not actual entity
var response = okResult.Value as SurveyRequestDtoWithId;
Assert.NotNull(response);
Assert.Equal(expected: response.Id, actual: addedSurveyRequest.Id);
Assert.Equal(expected: response.Name, actual: addedSurveyRequest.Name);
}
catch (Exception ex)
{
//Assert
Assert.False(true, ex.Message);
}
}
Controller side Post call for working test case:
[HttpPost("Insert")]
public IActionResult Post2([FromBody] SurveyRequest entity)
{
try
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
//var entity = _mapper.Map<SurveyRequestDto, SurveyRequest>(source: model);
//entity.CreateDate = System.DateTime.Now;
//entity.CreatedBy = 1;
var response = _repositories.SurveyRequest.Add(entity: entity); //This returns proper response with saved data and ID
_repositories.Save();
if (response.IsSuccess == true)
return new OkObjectResult(_mapper.Map<SurveyRequest, SurveyRequestDtoWithId>(source: response.Data));
else
return new ObjectResult(response.ErrorMessage) { StatusCode = 500 };
}
catch (Exception ex)
{
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
I'm not sure whether my test case setup for mapper is wrong or any other issue. I also tried lots of ways but no luck so far. So posting here if someone can look and help, will be much appreciated.
If you are using IMapper then you can do something like this:
var mapperMock = new Mock<IMapper>();
mapperMock
.Setup(mapper => mapper.Map<SurveyRequestDto, SurveyRequest>(It.IsAny< SurveyRequestDto>()))
.Returns(surveyRequest);
This solution does not utilize the AutoMapperProfile, but because you have only a single mapping that's why I think it not really a problem.
If you want to call Verify on the mapperMock then I would suggest to extract the Map selector delegate like this:
private static Expression<Func<IMapper, SurveyRequestDto>> MapServiceModelFromRequestModelIsAny =>
mapper => mapper.Map<SurveyRequestDto, SurveyRequest>(It.IsAny< SurveyRequestDto>());
Usage
//Arrange
mapperMock
.Setup(MapServiceModelFromRequestModelIsAny)
.Returns(surveyRequest);
...
//Assert
mapperMock
.Verify(MapServiceModelFromRequestModelIsAny, Times.Once);
UPDATE #1
It might also make sense to be as explicit as possible when you make assertion. If you want to you can do deep equality check to make sure that controller's parameter is not amended before the Map call:
private static Expression<Func<IMapper, SurveyRequestDto>> MapServiceModelFromRequestModel(SurveyRequestDto input) =>
mapper => mapper.Map<SurveyRequestDto, SurveyRequest>(It.Is< SurveyRequestDto>(dto => dto.deepEquals(input)));
//Assert
mapperMock
.Verify(MapServiceModelFromRequestModel(model), Times.Once);
This assumes that deepEquals is available as an extension method.
UPDATE #2
As it turned out the mock repository's Setup code also had some problem. Namely it used the surveyRequest rather than a It.IsAny<SurveyRequest>().
Because surveyRequest was specified as the expected parameter that's why the setupped code path is never called but returned with null.
After changed it to It.IsAny then the whole test started to work :D
repoMock
.Setup(repo => repo.SurveyRequest.Add(It.IsAny<SurveyRequest>()))
.Returns(new Response<SurveyRequest>(true, addedSurveyRequest))

Problem while trying to unit test a method that uses a mocked Task<TResult>

I am trying to unit test a method that sends some notifications using FCM.This method takes a list of batches and for each batch it creates a Task of IBatchResponse where IBatchResponse is an interface I have made so I can mock the return type of the actual service sending the notification. Then using Task.WhenAny() I am proccessing the tasks as they complete.
public static async Task<NotificationRes> SendPushNotifications(List<Batch> batches, INotificationService notificationService)
{
var notificationTasks = new List<Task<IBatchResponse>>();
try
{
foreach (var batch in batches)
{
notificationTasks.Add(notificationService.SendNotification(guardTokens, "android"));
}
while (notificationTasks.Count > 0)
{
var finishedTask = await Task.WhenAny(notificationTasks);
var taskIndex = notificationTasks.FindIndex(task => task == finishedTask);
notificationTasks.Remove(finishedTask);
var finishedTaskResult = await finishedTask;
for (int i = 0; i < finishedTaskResult.Responses.Count; i++)
{
....
}
}
catch (Exception)
{
throw;
}
}
And here is the code for the test I am trying to make.I have mocked the wrapper service that sends the notification and the return type of notificationService.SendNotification (IBatchResponse).
[Test]
public async Task SendPushNotifications_test()
{
...
var batchSendResponses = new List<ISendResponse>();
....
var mockNotifService = new Mock<INotificationService>();
var mockBatchRes = new Mock<IBatchResponse>();
mockBatchRes.Setup(res => res.Responses).Returns(batchSendResponses);
var task = Task.FromResult(mockBatchRes.Object);
mockNotifService.Setup(ns => ns.SendNotification(new string[5] { "token1", "token2", "", "", "" }, "android")).Returns(task);
var notRes = await Utils.SendPushNotifications(batches, mockNotifService.Object);
}
The problem is that if 2 batches exist the finishedTask Result is always null but if I have 1 batch it works.What am I missing here.
I have found the problem which is in the mockNotifService.Setup() method.
mockNotifService.Setup(ns => ns.SendNotification(new string[5] { "token1", "token2", "", "", "" }, "android")).Returns(task);
Currently in ns.SendNotification() I am hardcoding the params and this seems to affect the Task returned or it doesn't produce the task I specify in .Returns() and produces an empty one without the Result. So I changed this to:
mockNotifService.Setup(ns => ns.SendNotification(It.IsAny<string []>(), It.IsAny<string>())).Returns(Task.FromResult(mockBatchRes.Object));
and the test passed.

How mock methods of repository in Service for Unit test

I try to have a unit test for my service, I mocked everything needed, How I can Mock repository methods that Service is calling so that has value and code is not breaking,
This is my unit test:
public async Task Updateuser_ReturnsResponse()
{
// Arrange
var request = new UpdateUserRequest()
{
Guid = new Guid("92296ac1-f8e1-489a-a312-6ea9d31d60f8"),
FirstName = "TestFirst",
LastName = "TestLast",
PhoneWork = "9495467845",
EmailWork = "test123#yahoo.com",
};
var respose = new UpdateUserResponse()
{
Success = true
};
var getGuidRequest = new GetGuidRequest()
{
Guid = request.Guid
};
var getGuidResponse = new GetGuidResponse()
{
Guid = request.Guid
};
var mockUserRepository = new Mock<IUserRepository>();
var mockAwsProxy = new Mock<IAwsProxy>();
mockUserRepository.Setup(s => s.UpdateUserAsync(request)).ReturnsAsync(respose);
mockUserRepository.Setup(i => i.GetGuidAsync(getGuidRequest)).ReturnsAsync(getGuidResponse);
var sut = new FromService.UserService(....);
// Act
var response = await sut.UpdateUserAsync(request);
// Assert
Assert.NotNull(response);
Assert.True(response.Success);
}
My problem is when calling - var response = await sut.UpdateUserAsync(request); It goese to service and this GuidResponse is empty so it break after as shows GuidResponse Null:
public async Task<UpdateUserResponse> UpdateUserAsync(UpdateUserRequest request)
{
if (request.EmailWork.HasValue() || request.Role.HasValue())
{
var GuidResponse = await userRepository.GetGuidAsync(new GetGuidRequest
{
Guid = request.Guid
});
// it breaks here because GuidResponse is Null.
if (GuidResponse.Guid != null && request.EmailWork.HasValue())
{
.......
It fails because the setup does not match what was actually given to the mock when the test was exercised.
Use It.Is<T>() to match the passed argument parameter
//...omitted for brevity
mockUserRepository
.Setup(_ => _.GetGuidAsync(It.Is<GetGuidRequest>(x => x.Guid == request.Guid)))
.ReturnsAsync(getGuidResponse);
//...omitted for brevity
assuming the mocked repository is what was injected into the SUT
Reference Moq Quickstart: Matching Arguments

Net Core: Moq Method with Generic Class T and Parameters

I am trying to Moq this method below. How would I moq a method with a Generic Class T? Below is method and currently attemping solution,
Method:
public async Task<TV> HttpPost<T, TV>(string url, T prm, string accessToken = null)
{
try
{
if ((accessToken ?? "").Length > 0)
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using (var result = await _client.PostAsJsonAsync(url, prm))
{
result.EnsureSuccessStatusCode();
using (var content = result.Content)
{
var ret = await content.ReadAsJsonAsync<TV>();
return ret;
}
}
}
catch (Exception ex)
{
var message = $"Url: {url}, Data: {JsonConvert.SerializeObject(prm)}";
ex.Data["Url"] = message;
throw;
}
}
Attempt:
var mock = new Mock<HttpDataLayerUtilsAsync>();
mock.Setup(b => b.HttpPost(It.IsAny<string>, It.IsAny <CustomDataModelRequest>).Returns..
Resource:
Mocking generic method call for any given type parameter
You need to supply the generic arguments to the HttpPost in the Setup, for example:
// T TV
.Setup(b => b.HttpPost<CustomDataModelRequest, CustomDataModelResponse>(
It.IsAny<string>(),
It.IsAny<CustomDataModelRequest>(),
null))
As well as passing a value for the optional parameter.
For the Return you can use:
.ReturnsAsync(new CustomDataModelResponse());
Then you can call it like so:
var result = await mock.Object.HttpPost<CustomDataModelRequest, CustomDataModelResponse>("", new CustomDataModelRequest(), "");

How to test Automatonymous state machines?

I'm trying to write tests for an Automatonymous state machine, but I'm having a fair bit of trouble getting it right, and I've found very little documentation.
Here's what I have at the moment for one test:
[TestFixture]
public class MyProcessStateMachineTests
{
InMemoryTestHarness _Harness;
MyProcessStateMachine _Machine;
StateMachineSagaTestHarness<MyProcess, MyProcessStateMachine> _Saga;
[OneTimeSetUp]
public void ConfigureMessages()
{
MessageCorrelation.UseCorrelationId<RequestMyDetails>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<FileAttached>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<PDFGenerated>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<CustomerAttachFile>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<AddCustomerNote>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<EmailPublished>(x => x.CorrelationId);
}
[SetUp]
public void InitializeTestHarness()
{
_Harness = new InMemoryTestHarness();
_Machine = new MyProcessStateMachine( /* snip */ );
_Saga = _Harness.StateMachineSaga<MyProcess, MyProcessStateMachine>(_Machine);
_Harness.Start().Wait();
}
[TearDown]
public void StopTestHarness()
{
_Harness.Stop();
}
[Test]
public async Task ShouldAttachToCustomer()
{
var sagaId = Guid.NewGuid();
var custId = Guid.NewGuid();
var fileAttached = BuildFileAttachedMessage(sagaId);
await _Harness.InputQueueSendEndpoint.Send(BuildStartMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(BuildDetailsReceivedMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(BuildPdfGeneratedMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(fileAttached);
// Next line is based on [the answer here][1]
// Once the above messages are all consumed and processed,
// the state machine should be in AwaitingEmail state
await _Saga.Match(x =>
x.CorrelationId == sagaId
&& x.CurrentState == _Machine.AwaitingEmail.Name,
new TimeSpan(0, 0, 30));
// Grab the instance and Assert stuff...
}
// Snip...
}
Given that the _Saga.Match call finds a match, I would expect that all messages have been processed and I should be able to grab my state machine instance and published events and check their values - but that isn't the case. When I run the tests in the fixture, sometimes the instance I get has consumed and published the expected messages; sometimes it's not quite there yet.
I've tried grabbing my instance using:
var inst = _Saga.Sagas.FirstOrDefault(x => x.Saga.CorrelationId == sagaId);
or grabbing published events with:
var test = _Harness.Published
.FirstOrDefault(x => x.MessageType == typeof(IAttachFile) && x.Context.CorrelationId == sagaId);
but it doesn't matter that the call to Match succeeded, the state machine instance (and published events) aren't always present.
I'm assuming that the async proccesses from Automatonymous, MassTransit, or test harness is causing the inconsistency. Any help?
Testing with MassTransit, MassTransit.Automatonymous and MassTransit.TestFramework 5.1.2.1528, Automatonymous 4.1.1.102,
EDIT:
Further review, I've found that when I have a problem, the call to Match( ... )
didn't succeed - it timed out. (I had been incorrectly assuming that a timeout would throw an exception.)
For anyone hitting the same issue: the way you try to get the instance
var inst = _Saga.Sagas.FirstOrDefault(x => x.Saga.CorrelationId == sagaId);
can be fixed like this
var instanceIds = await _sagaHarness.Match(
instance => instance.CorrelationId == sagaId
&& instance.CurrentState == _machine.Working.Name,
new TimeSpan(0, 0, 30));
It is important that the expected state is included in the condition. Just fetching the instance by correlationid and then testing CurrentState may fail, as setting state (saga execution being async) may take some time.
See full example at https://github.com/balintn22/AutomatonymousTestExample
In case this might be helpful to someone else, this is how I eventually got it working:
[TestFixture]
public class ProcessStateMachineTests : InMemoryTestFixture
{
TimeSpan _TestTimeout = new TimeSpan(0, 1, 0);
ProcessStateMachine _Machine;
InMemorySagaRepository<Process> _Repository;
protected override void ConfigureInMemoryReceiveEndpoint(
IInMemoryReceiveEndpointConfigurator configurator)
{
_Machine = new ProcessStateMachine();
_Repository = new InMemorySagaRepository<Process>();
configurator.StateMachineSaga(_Machine, _Repository);
}
[OneTimeSetUp]
public void ConfigureMessages()
{
// Message correlation and such happens in here
ProcessStateMachine.ConfigureMessages();
}
[Test]
public async Task OnInitializationIStartProcessIsConsumed()
{
var sagaId = Guid.NewGuid();
var customerId = Guid.NewGuid();
await SetupStateMachine(sagaId, customerId, _Machine.AwaitingDetails.Name);
var msg = InMemoryTestHarness.Consumed
.Select<IStartProcess>(x => x.Context.Message.RequestId == sagaId)
.FirstOrDefault();
// Assert against msg for expected results
}
[Test]
public async Task OnStartProcessAddCustomerNoteAndRequestDetailsPublished()
{
var sagaId = Guid.NewGuid();
var customerId = Guid.NewGuid();
await SetupStateMachine(sagaId, customerId, _Machine.AwaitingDetails.Name);
var pubdNoteAddedMsg = InMemoryTestHarness.Published
.Select<IAddCustomerNote>()
.FirstOrDefault(x => x.Context.Message.RequestId == sagaId);
var pubdDetailsReqdMsg = InMemoryTestHarness.Published
.Select<IRequestDetails>()
.FirstOrDefault(x => x.Context.Message.RequestId == sagaId);
Assert.IsTrue(pubdNoteAddedMsg != null);
Assert.IsTrue(pubdDetailsReqdMsg != null);
Assert.AreEqual(sagaId, pubdNoteAddedMsg.Context.CorrelationId);
Assert.AreEqual(sagaId, pubdDetailsReqdMsg.Context.CorrelationId);
Assert.AreEqual(customerId, pubdNoteAddedMsg.Context.Message.CustomerId);
Assert.IsFalse(String.IsNullOrEmpty(pubdNoteAddedMsg.Context.Message.Note));
}
private async Task SetupStateMachine(
Guid sagaId,
Guid customerId,
String toState)
{
if (String.IsNullOrEmpty(toState))
return;
await MoveStateMachineForward(BuildStartMessage(), x => x.AwaitingDetails);
var awaitingDetailsId = await _Repository.ShouldContainSagaInState(
sagaId, _Machine, x => x.AwaitingDetails, _TestTimeout);
Assert.IsNotNull(awaitingDetailsId, "Error, expected state machine in AwaitingDetails state");
if (toState == _Machine.AwaitingDetails.Name)
return;
// ...and more stuff to move to later states, depending on
// where I want my test's starting point to be...
async Task MoveStateMachineForward<T>(
T message,
Func<ProcessStateMachine, Automatonymous.State> targetState)
where T : class
{
await InputQueueSendEndpoint.Send(message);
var foundSagaId = await _Repository.ShouldContainSagaInState(
sagaId, _Machine, targetState, _TestTimeout);
Assert.IsTrue(foundSagaId.HasValue);
}
IStartProcess BuildStartMessage()
{
return new StartProcessMessage(sagaId, customerId);
}
}
}

Categories

Resources