Using the InMemoryTestHarness, I am working on unit testing a Consumer that handles a command with a MessageData property. Using the InMemoryMessageDataRepository to populate the MessageData property works fine however, when the consumer attempts to load the payload (message.Body.Value) I am receiving the following exception: "The message data was not loaded: urn:msgdata:xxxxxxxxxxxxxxxxxxxxxxxxxx"}. What is the proper way to unit test a consumer that handles commands with a MessageData property?
I was going to use the In-memory transport, but it doesn't seem like the right approach since the unit test(s) will not receive immediate feedback from the consumer. Ultimately the end goal is for my unit tests to be able assert that specific exceptions are thrown or that the consumer completed successfully without errors.
using System;
using System.Linq;
using System.Threading.Tasks;
using MassTransit;
using MassTransit.MessageData;
using MassTransit.Testing;
using Newtonsoft.Json;
using Xunit;
namespace Test
{
public class LargePayloadConsumerTest
{
[Fact]
public async Task Consumer_Should_Deserialize_Message()
{
var harness = new InMemoryTestHarness();
var consumer = harness.Consumer<BigMessageConsumer>();
var command = new BigMessageCommand();
var dataRepo = new InMemoryMessageDataRepository();
var largePayload = new BigMessagePayload()
{
Id = 1,
FirstName = "TestFirstName",
MiddleName = "TestMiddleName",
LastName = "TestLastName"
};
var json = JsonConvert.SerializeObject(largePayload);
command.Body = await dataRepo.PutString(json);
var thisWorks = await command.Body.Value;
await harness.Start();
await harness.InputQueueSendEndpoint.Send(command);
Assert.True(harness.Consumed.Select<BigMessageCommand>().Any());
}
public class BigMessageConsumer : IConsumer<BigMessageCommand>
{
public async Task Consume(ConsumeContext<BigMessageCommand> context)
{
try
{
var json = await context.Message.Body.Value;
var deserialized = JsonConvert.DeserializeObject<BigMessagePayload>(json);
Console.WriteLine($"{deserialized.Id}: {deserialized.FirstName} {deserialized.MiddleName} {deserialized.LastName}");
}
catch (Exception e)
{
//throws {"The message data was not loaded: urn:msgdata:xxxxxxxxxxxxxxxxxxxxxxxxxx"}
Console.WriteLine(e);
throw;
}
}
}
public class BigMessageCommand
{
public MessageData<string> Body { get; set; }
}
public class BigMessagePayload
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
}
}
You need to configure the receive endpoint to handle the message data property, similar to this:
e.UseMessageData<BigMessageCommand>(dataRepo);
Since you're using the test harness, you can add it to the receive endpoint before you add the consumer test harness:
void ConfigureReceiveEndpoint(IReceiveEndpointConfigurator configurator)
{
configurator.UseMessageData<BigMessageCommand>(dataRepo);
}
harness.OnConfigureReceiveEndpoint += ConfigureReceiveEndpoint;
Related
I have a piece of C# code that reacts to an HTTP trigger from Azure and gets executed. The piece of code sends an email containing a warning:
#r "Newtonsoft.Json"
#r "SendGrid"
using System;
using SendGrid.Helpers.Mail;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static SendGridMessage Run(HttpRequest req, ILogger log)
{
string requestBody = new StreamReader(req.Body).ReadToEnd();
log.LogInformation(requestBody);
var notifications = JsonConvert.DeserializeObject<IList<Notification>>(requestBody);
SendGridMessage message = new SendGridMessage();
message.Subject = "Sensor Anomaly Warning";
var content = "The following device has started registering anomalies:<br/><br/><table><tr><th>time</th><th>value</th><th>lower bound</th><th>upper bound</th><th>device</th></tr>";
foreach(var notification in notifications) {
log.LogInformation($" - time: {notification.time}, value: {notification.value}, lowerbound: {notification.lowerbound}, upperbound: {notification.upperbound}, device: {notification.device}");
content += $"<tr><td>{notification.time}</td><td>{notification.value}</td><td>{notification.lowerbound}</td><td>{notification.upperbound}</td><td>{notification.device}</td></tr>";
}
content += "</table>";
message.AddContent("text/html", content);
return message;
}
public class Notification
{
public string time { get; set; }
public string value { get; set; }
public string lowerbound { get; set; }
public string upperbound { get; set; }
public string device { get; set; }
}
Now, I want to execute this same piece of code that sends an email, but based on the value of notification.alert which stores a zero if an anomaly started and a 1 if the alert stopped. Coming from python, this would be as easy as setting an "if else" statement, where the function is called in each case.
However for this C# code, there is no function to call. The piece of code sends an email but it's just creating a class. In any case, I'm wondering if I can use a "if else" statement in C# based on the value of notification.alert that in one case sends an email saying something like "the device has started registering anomalies" and in the other "the device has stopped registering anomalies". I just can't get to do this, as I have to address the notification object, which is already inside the class.
I must say that I am not a C# developer but a Python developer, hence the doubts.
You can use if/else statement or switch to update the email body based on notification.alert. this function is not sending the email it's just setting up a message for the email body text.
using System;
using SendGrid.Helpers.Mail;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static SendGridMessage Run(HttpRequest req, ILogger log)
{
string requestBody = new StreamReader(req.Body).ReadToEnd();
log.LogInformation(requestBody);
var notifications = JsonConvert.DeserializeObject<IList<Notification>>(requestBody);
SendGridMessage message = new SendGridMessage();
message.Subject = "Sensor Anomaly Warning";
var content = "The following device has started registering anomalies: <br/><br/><table><tr><th>time</th><th>value</th><th>lower bound</th><th>upper bound</th><th>device</th></tr>";
foreach(var notification in notifications) {
log.LogInformation($" - time: {notification.time}, value: {notification.value}, lowerbound: {notification.lowerbound}, upperbound: {notification.upperbound}, device: {notification.device}");
content += notification.alert == 0 ? "the device has started registering anomalies" : "the device has stopped registering anomalies";
}
content += "</table>";
message.AddContent("text/html", content);
return message;
}
public class Notification
{
public string time { get; set; }
public string value { get; set; }
public string lowerbound { get; set; }
public string upperbound { get; set; }
public string device { get; set; }
public int alert { get; set;}
}
I'm using xunit unit tests to test the MassTransit consumers, in most cases it works but in some cases the "context.message" in the consumer is filled with an object containing empty properties. (all string are null, guid is 000-0000.. etc.) The unit tests compared between running and not running tests looks identical.
a) Command (Message)
public record UpdateKontakteintragCommand : ApplicationCommandRequest
{
public string Bemerkung { get; init; }
public Guid Id { get; set; }
// TODO: Fallaktenzuordnungsdatum
}
b) Unit Test:
[Fact]
public async Task UpdateKontakteintrag_Complete_AcceptedMessage()
{
var consumer = harness.Consumer(() => new UpdateKontakteintragCommandConsumer(kontakteintragRep));
await harness.Start();
try
{
var requestClient = await harness.ConnectRequestClient<UpdateKontakteintragCommand>();
var command = new UpdateKontakteintragCommand { Id = nonDeletedGuid, Bemerkung = "abc", };
await requestClient.GetResponse<AcceptedResponse, FaultedResponse>(command);
Assert.True(consumer.Consumed.Select<UpdateKontakteintragCommand>().Any());
Assert.True(harness.Sent.Select<AcceptedResponse>().Any());
}
finally
{
await harness.Stop();
}
}
c) Consumer
public class UpdateKontakteintragCommandConsumer : IConsumer<UpdateKontakteintragCommand>
{
private readonly IKontakteintraegeDomainRepository kontakteintragRepository;
public UpdateKontakteintragCommandConsumer(IKontakteintraegeDomainRepository kontakteintragRepository)
=> this.kontakteintragRepository = kontakteintragRepository;
public async Task Consume(ConsumeContext<UpdateKontakteintragCommand> context)
{
var kontakteintrag = kontakteintragRepository.Get(context.Message.Id);
No the "context.Message.Id" is 0000-0000 ... but in the unit test it is definitly set. Any ideas?
I have a unit test that is basically testing the behaviour of EF Core. The class I am trying to test looks like this:
namespace MusicPortal.Repository.Repository
{
public class ArtistRepository : IArtistRepository
{
private readonly MusicPortalDbContext _context;
public ArtistRepository(MusicPortalDbContext context)
{
_context = context;
}
public async Task<MusicPortalDatabaseResponse<bool>> AddNewArtist(Artist artist)
{
try
{
await _context.Artists.AddAsync(new Artist
{
ArtistType = ArtistTypes.Band,
City = artist.City,
Country = artist.Country,
Genre = artist.Genre,
Name = artist.Name,
ProfileImageUrl = artist.ProfileImageUrl
});
_context.SaveChanges();
return new MusicPortalDatabaseResponse<bool>
{
HasError = false,
Exception = null,
Response = true
};
}
catch (Exception e)
{
return new MusicPortalDatabaseResponse<bool>
{
HasError = true,
Exception = e,
Response = false
};
}
}
}
}
And I have the following Unit Test for it using Moq
namespace MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist
{
[TestFixture]
public class GivenAddingANewArtistToADatabaseFails
{
private Mock<DbSet<Artist>> _mockArtistDbSet;
private Mock<MusicPortalDbContext> _mockContext;
private IArtistRepository _artistRepository;
private MusicPortalDatabaseResponse<bool> _addArtistToDbResponse;
[OneTimeSetUp]
public async Task Setup()
{
_mockArtistDbSet = new Mock<DbSet<Artist>>();
_mockContext = new Mock<MusicPortalDbContext>();
_mockArtistDbSet
.Setup(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()))
.Callback((Artist artist, CancellationToken token) => { })
.ReturnsAsync(It.IsAny<EntityEntry<Artist>>());
_mockContext
.Setup(x => x.SaveChanges())
.Throws(new Exception("Cannot save new Artist to Database"));
_artistRepository = new MusicPortal.Repository.Repository.ArtistRepository(_mockContext.Object);
_addArtistToDbResponse = await _artistRepository.AddNewArtist(It.IsAny<Artist>());
}
[Test]
public void ThenANegativeResultIsReturned() // pass
{
Assert.IsFalse(_addArtistToDbResponse.Response);
Assert.IsTrue(_addArtistToDbResponse.HasError);
Assert.IsInstanceOf<Exception>(_addArtistToDbResponse.Exception);
}
[Test]
public void ThenTheArtistContextAddMethodIsCalledOnce() //fail
{
_mockArtistDbSet.Verify(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public void ThenTheArtistsContextSaveMethodIsNeverCalled() //pass
{
_mockContext.Verify(x => x.SaveChanges(), Times.Never);
}
}
}
The first and last assertion pass but ThenTheArtistContextAddMethodIsCalledOnce() fails due to the following error:
MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist.GivenAddingANewArtistToADatabaseFails.ThenTheArtistContextAddMethodIsCalledOnce
Moq.MockException :
Expected invocation on the mock once, but was 0 times: x => x.AddAsync(It.IsAny(), It.IsAny())
Performed invocations:
Mock:1> (x):
No invocations performed.
at Moq.Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
at Moq.Mock1.Verify[TResult](Expression1 expression, Func`1 times)
at MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist.GivenAddingANewArtistToADatabaseFails.ThenTheArtistContextAddMethodIsCalledOnce() in MusicPortal.Tests\Repository\ArtistRepository\AddNewArtist\GivenAddingANewArtistToADatabaseFails.cs:line 53
I'm understanding that the problem code is c#
_mockArtistDbSet
.Setup(x => x.AddAsync(It.IsAny<Artist>(), It.IsAny<CancellationToken>()))
.Callback((Artist artist, CancellationToken token) => { })
.ReturnsAsync(It.IsAny<EntityEntry<Artist>>());
And I know the problem is most likely due to async issue but I don't know why, or what the actual problem is. Any advice, solutions?
You declare and setup _mockArtistDbSet, but you don't use/attach it to the _mockContext. I think you need to add something like:
_mockContext.Setup(m => m.Artist).Returns(_mockArtistDbSet.Object);
So it looks like EF Core is not so easily tested with async tasks such as SaveChangesAsync and AddAsync. In the end, I followed the MS guide for testing EF core and created a mock context. The only downside being I can only test happy path. Although error paths are tested by the service which consumes the repository, I was hoping for more test coverage on the repository layer.
Anyway, here's the spec
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MusicPortal.Core.Context;
using MusicPortal.Core.Repository;
using MusicPortal.Tests.Repository.ArtistRepository.TestHelpers;
using MusicPortal.Tests.Repository.ArtistRepository.TestHelpers.MockDB;
using NUnit.Framework;
using MockArtistRepository = MusicPortal.Repository.Repository.ArtistRepository;
namespace MusicPortal.Tests.Repository.ArtistRepository.AddNewArtist
{
[TestFixture]
public class GivenANewArtistToInsertIntoTheDb
{
private DbContextOptions<MusicPortalDbContext> _options;
private MusicPortalDatabaseResponse<bool> _mockResponse;
[OneTimeSetUp]
public async Task Setup()
{
_options = new MockDbFactory("MusicPortalDB").Options;
using (var context = new MusicPortalDbContext(_options))
{
var artistRepository = new MockArtistRepository(context);
_mockResponse = await artistRepository.AddNewArtist(MockRepositoryData.Artist);
}
}
[Test]
public void AndThenAPositiveResultIsReturned()
{
Assert.Null(_mockResponse.Exception);
Assert.IsTrue(_mockResponse.Response);
Assert.IsFalse(_mockResponse.HasError);
}
[Test]
public void ThenTheArtistShouldBeSavedWithNoProblem()
{
using (var context = new MusicPortalDbContext(_options))
{
Assert.AreEqual(1, context.Artists.Count());
}
}
}
}
and the Mock Database:
using System;
using Microsoft.EntityFrameworkCore;
using MusicPortal.Core.Context;
using MusicPortal.Core.DBModels;
namespace MusicPortal.Tests.Repository.ArtistRepository.TestHelpers.MockDB
{
public sealed class MockDbFactory
{
public DbContextOptions<MusicPortalDbContext> Options { get; }
public MockDbFactory(string dbName)
{
Options = new DbContextOptionsBuilder<MusicPortalDbContext>()
.UseInMemoryDatabase(dbName)
.Options;
}
public void AddArtistsToContext()
{
using (var context = new MusicPortalDbContext(Options))
{
context.Artists.Add(new Artist
{
City = "Orange County",
Country = "USA",
Events = null,
Genre = "Pop Punk",
Id = Guid.Parse("8a07504b-8152-4d8b-8e21-74bf64322ebc"),
Merchandise = null,
Name = "A Day To Remember",
ArtistType = "Band",
ProfileImageUrl = "https://placehold.it/30x30"
});
context.SaveChanges();
}
}
}
}
I hope this helps anyone looking at the same issue. The lesson learned is don't use Async unless you absolutely have to.
My bot receives some user info like order no and few other details based on that it searches the orders. If more that 1 order is returned then it asks user to provide more info so asks for 2 additional details.
Now my problem is that as I have already build the form using FormBuilder and on completion I ask user to provide more info so that should be the approach to ask for additional info. Should I create another form and request info or is there any way to add the additional fields to the same form in the completion method.
I want to ask for more user inputs after initial search is complete and msg for more than 1 order found is displayed.
As my properties SubOrderNumber & SubOrderVersion are in OrderQuery class so Should i build new form for OrderQuery or is there anyother way to add these two to existing Order form.
using System;
using Microsoft.Bot.Builder.FormFlow;
namespace Bot
{
[Serializable]
public class OrderSearchQuery
{
[Prompt("Please enter {&}")]
public string OrderNumber { get; set; }
[Prompt("Please enter {&}?")]
public String Location { get; set; }
[Prompt("Please provide last status {&}?")]
public string Status{ get; set; }
[Prompt("Please enter {&}?")]
public string SubOrderNumber { get; set; }
[Prompt("Please enter {&}?")]
public string SubOrderVersion { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Bot.Connector;
namespace Bot.Dialogs
{
[Serializable]
public class OrderDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Order helper!");
var orderFormDialog = FormDialog.FromForm(this.BuildOrderForm, FormOptions.PromptInStart);
context.Call(orderFormDialog, this.ResumeAfterOrdersFormDialog);
}
private IForm<OrderSearchQuery> BuildOrderForm()
{
OnCompletionAsyncDelegate<OrderSearchQuery> processOrderSearch = async (context, state) =>
{
await context.PostAsync($"Ok. Searching for Orders with Number: {state.OrderNumber}...");
};
return new FormBuilder<OrderSearchQuery>()
.Field(nameof(OrderSearchQuery.OrderNumber))
.AddRemainingFields(new string[] { nameof(RequiredDWPSearchQuery.SubOrderNumber), nameof(RequiredDWPSearchQuery.SubOrderVersion) })
.OnCompletion(processOrderSearch)
.Build();
}
private async Task ResumeAfterOrdersFormDialog(IDialogContext context, IAwaitable<OrderSearchQuery> result)
{
try
{
var searchQuery = await result;
await context.PostAsync($"I found total of 100 Ordes");
await context.PostAsync($"To get Order details, you will need to provide more info...");
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the Required DWP Search";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
context.Done<object>(null);
}
}
}
}
While there are conditional parameters for fields and the possibility of doing logic in validation, I don't believe that would be the cleanest way to go. I would take a two dialog approach.
Here's the original form, with additional parameters removed:
using Microsoft.Bot.Builder.FormFlow;
using System;
namespace TestChatbot.Dialogs
{
[Serializable]
public class OrderSearchQuery
{
[Prompt("Please enter {&}")]
public string OrderNumber { get; set; }
[Prompt("Please enter {&}?")]
public String Location { get; set; }
[Prompt("Please provide last status {&}?")]
public string Status { get; set; }
}
}
Instead, I moved the additional properties to a new form:
using Microsoft.Bot.Builder.FormFlow;
using System;
namespace TestChatbot.Dialogs
{
[Serializable]
public class OrderFollowUp
{
[Prompt("Please enter {&}?")]
public string SubOrderNumber { get; set; }
[Prompt("Please enter {&}?")]
public string SubOrderVersion { get; set; }
}
}
Then I added logic to handle the second form, depending on the results of the search:
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
namespace TestChatbot.Dialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Order helper!");
var orderFormDialog = FormDialog.FromForm(this.BuildOrderForm, FormOptions.PromptInStart);
context.Call(orderFormDialog, this.ResumeAfterOrdersFormDialog);
}
private IForm<OrderSearchQuery> BuildOrderForm()
{
return new FormBuilder<OrderSearchQuery>()
.Build();
}
private IForm<OrderFollowUp> BuildFollowUpForm()
{
return new FormBuilder<OrderFollowUp>()
.Build();
}
private async Task ResumeAfterOrdersFormDialog(IDialogContext context, IAwaitable<OrderSearchQuery> result)
{
try
{
var searchQuery = await result;
await context.PostAsync($"Ok. Searching for Orders with Number: {searchQuery.OrderNumber}...");
await context.PostAsync($"I found total of 100 Ordes");
int searchResultCount = 2;
if (searchResultCount > 1)
{
await context.PostAsync($"To get Order details, you will need to provide more info...");
var followUpDialog = FormDialog.FromForm(this.BuildFollowUpForm, FormOptions.PromptInStart);
context.Call(followUpDialog, this.ResumeAfterFollowUpDialog);
}
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the Required DWP Search";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
}
private async Task ResumeAfterFollowUpDialog(IDialogContext context, IAwaitable<OrderFollowUp> result)
{
await context.PostAsync($"Order complete.");
context.Done<object>(null);
}
}
}
The difference here is that I removed the OnCompletion call and handled that logic on the resumption handler, after the first dialog completes. Based on the search result, you can call the second dialog, much like you did the first to extract additional information.
I am using the Twilio REST API helper library, v 5.0.1 in my C# ASP.NET MVC Web Application. I created the following helper class and function to send out text messages:
using MyApplication.Web.Helpers;
using System;
using System.Configuration;
using Twilio;
using Twilio.Exceptions;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
namespace MyApplication.Web.Services
{
public class TwilioSmsSender : ISmsSender
{
public string AccountSid { get; set; }
public string AuthToken { get; set; }
public string FromPhoneNumber { get; set; }
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public string SmsPrefix { get; set; }
public string SmsSuffix { get; set; }
public TwilioSmsSender()
{
//get our Twilio account info from the config file
AccountSid = ConfigurationManager.AppSettings["TwilioAccountSid"];
AuthToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
FromPhoneNumber = ConfigurationManager.AppSettings["SmsService.FromPhoneNumber"];
SmsPrefix = ConfigurationManager.AppSettings["SmsPrefix"];
SmsSuffix = ConfigurationManager.AppSettings["SmsSuffix"];
if (FromPhoneNumber.Length == 10)
{
FromPhoneNumber = $"+1{FromPhoneNumber}";
}
TwilioClient.Init(AccountSid, AuthToken);
}
public INotificationResponse SendTextMessage(string phoneNumber, string message, bool useFormatting = true)
{
var resp = new TwilioSmsSenderResponse();
resp.Succeeded = false;
resp.AttemptDateTimeUtc = DateTime.UtcNow;
if (useFormatting)
{
message = $"{SmsPrefix}{message}{SmsSuffix}";
}
try
{
var msgResponse = MessageResource.Create(
to: new PhoneNumber($"+1{phoneNumber}"),
from: new PhoneNumber($"{FromPhoneNumber}"),
body: message);
//Previous line works (i.e, I get the text message that I'm sending out successfully).
//However, none of the following lines are running...
//As you see, this is in a try/catch block... and it doesn't go to the catch block either!
if (msgResponse.ErrorCode == null)
{
//successfully queued
resp.Succeeded = true;
resp.ReferenceId = msgResponse.Sid;
resp.AttemptDateTimeUtc = DateTime.UtcNow;
}
else
{
//Twilio sent an error back
log.Info($"Twilio sent an error back: {msgResponse}");
resp.Succeeded = false;
resp.Notes = $"ErrorCode: {msgResponse.ErrorCode}, ErrorMessage: {msgResponse.ErrorMessage}";
resp.AttemptDateTimeUtc = DateTime.UtcNow;
}
}
catch (Exception e)
{
resp.Succeeded = false;
resp.Notes = ExceptionsHelper.GetExceptionDetailsAsString(e);
resp.AttemptDateTimeUtc = DateTime.UtcNow;
log.Error($"Twilio Error: {resp.Notes}, to: {phoneNumber}, message: {message}");
}
return resp;
}
}
}
Unfortunately, my code is not behaving as I expected it would after the MessageResource.Create() call. That is, the text-message is sent out correctly and I receive the SMS on my phone. However, I expect the call to return control to my msgResponse variable and I expect the
if (msgResponse.ErrorCode == null) ...
line and subsequent lines to run but that is not happening. I can put a breakpoint on the var msgResponse line and it will run that just fine but it does not run any code lines after that. You’ll see that I have the call in a try/catch. I suspected there was an exception that was occurring but it doesn’t seem so because it doesn’t go to my catch block either! The text message is being sent successfully! All I want to do is to get an acknowledgement back so that I can properly log it and send that information back to the routines that are calling this function.
Any help would be greatly appreciated!
version 5.0.2 fixed this for me just update twilio to 5.0.2. they just added .ConfigureAwait(false); with CreateAsync