Automated Testing of Twilio C# Apps with Moq
Time to read: 5 minutes
Automating testing of apps that rely on third-party API's like Twilio can be challenging. Let's look at how structure your C# app and write tests using Moq to achieve automated testing nirvana. All of the example code in this guide is available on GitHub.
Set Things Up
Create a Test Project
We'll use Visual Studio's built-in test projects for this guide, but the practices here translate readily to alternative testing frameworks such as NUnit and xUnit.
To add a new test project to your existing solution, right-click on your solution in the Solution Explorer and select Add... New Project:
Select a Unit Test project and name your new project:
Don't forget to add project references to other projects in the solution that you want to test:
Install the Moq Mocking Framework
When testing our services in isolation we'll create mock objects. Mock objects are the crash-test dummies of software development.
Let's install the mocking framework Moq using Nuget:
Install-Package Moq
Make sure before you run that command the "Default project" is set to your test project:
Improve Your App's Testability
Decouple Code That Makes External API Calls
Let's say you're building an OrderService
class that needs to process orders for items. It needs to reserve inventory, ship the order, and notify the customer via SMS that their order has been shipped. You could call the Twilio API to send the SMS directly from your service. Your code might look like this:
public void ProcessOrder(IOrder order)
{
TryReserveInventory(order);
var trackingNum = ShipOrder(order);
var message = $"Your order {order.Id} has shipped! Tracking: {trackingNum}";
var accountSid = ConfigurationManager.AppSettings["TwilioAccountSid"];
var authToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
var fromPhoneNumber = ConfigurationManager.AppSettings["TwilioFromNumber"];
Twilio.Init(accountSid, authToken);
MessageResource.Create(
to: new PhoneNumber(order.Customer.MobileNumber),
from: new PhoneNumber(fromPhoneNumber),
body: message);
}
The problem is that your OrderService
is tightly coupled to the Twilio API. Tight coupling is bad for a number of reasons, but it's especially bad for testability. What we need is a simple NotificationService
that can handle talking to the Twilio API. This will allow us to test our OrderService
in isolation without actually sending a text message.
Now our code can look something like:
public void ProcessOrder(IOrder order)
{
TryReserveInventory(order);
var trackingNum = ShipOrder(order);
_notificationService.SendText(order.Customer.MobileNumber,
$"Your order {order.Id} has shipped! Tracking: {trackingNum}");
}
And here's the full code of our new NotificationService
:
Notice that it implements an INotificationService
interface, which will come in handy for mocking later.
namespace TwilioStore.Interfaces.Services
{
public interface INotificationService
{
void SendText(string to, string message);
}
}
Decouple API Credential Configuration
There's one more change we should make to improve our testability. In our notification service, we're loading configuration values like so:
var accountSid = ConfigurationManager.AppSettings["TwilioAccountSid"];
var authToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
...
var fromPhoneNumber = ConfigurationManager.AppSettings["TwilioFromNumber"];
When we test NotificationService
, we don't want to be tightly coupled to the configuration system. In fact, we might even want to test our service with different values for different tests. So, let's extract a new class we'll call NotificationConfiguration
:
Again, we created an interface for this class:
namespace TwilioStore.Interfaces.Services
{
public interface INotificationConfiguration
{
string AccountSid { get; }
string AuthToken { get; }
string DefaultFromPhoneNumber { get; }
}
}
Now our NotificationService
can make use of this configuration class to get the required variables. Notice below how we allow passing in a custom INotificationConfiguration
. If none is passed in, it defaults to our NotificationConfiguration
implmentation. For fun, let's also add MakePhoneCall()
and BuyPhoneNumber()
methods to our service.
Here's the completed code:
And the final interface:
namespace TwilioStore.Interfaces.Services
{
public interface INotificationService
{
void SendText(string to, string message);
void MakePhoneCall(string to, string voiceUrl);
void BuyPhoneNumber(string number);
}
}
Test Your App's Logic with Moq
Now, let's test our ProcessOrder()
method. We just want to make sure that it calls our NotificationService.SendText()
method with the right values, but we don't need to actually talk to the Twilio API. Let's use Moq to create a mock INotificationService
:
var notificationServiceMock = new Mock<INotificationService>();
string textMessage = null;
notificationServiceMock.Setup(
x => x.SendText(ValidToNumber, It.IsAny<string>())
)
.Callback<string, string>((_, message) => textMessage = message);
The call to Setup()
tells our mock to expect a call to the SendText()
method with the first parameter a constant we defined to represent a valid customer's phone number and any string for the second, message parameter. We then provide a callback method that saves the message into a variable for later inspection.
Using the mock object is as easy as passing it into our OrderService
:
var orderService = new OrderService(notificationServiceMock.Object);
orderService.ProcessOrder(fakeOrder);
Finally, we can assert that the SendText()
method was called exactly once:
notificationServiceMock.Verify(
x => x.SendText(ValidToNumber, It.IsAny<string>()),
Times.Once
);
Here's the complete code for our test:
Test Your External API Classes
Since your application logic will likely have many tests, it makes sense for the tests not to hit an external API every time. But what about the NotificationService
? Where's the test coverage love for him?
Thankfully, Twilio provides test credentials that we can use to allow us to write a suite of tests to exercise our service. Our approach will be to create a mock of INotificationConfiguration
that provides our test credentials instead of reading them from a configuration file:
private static INotificationConfiguration GetTestConfig()
{
var configMock = new Mock<INotificationConfiguration>();
configMock.SetupGet(x => x.AccountSid)
.Returns(TestAccountSid);
configMock.SetupGet(x => x.AuthToken)
.Returns(TestAuthToken);
configMock.SetupGet(x => x.DefaultFromPhoneNumber)
.Returns(ValidFromNumber);
return configMock.Object;
}
Test Sending an SMS
As documented in our API Reference, when you use your test credentials there are special phone numbers you can use for your testing. For SMS tests you can send a text to any valid phone number and you will receive a successful response.
Here's how we could test that, passing in the mock config object we created above:
[TestMethod]
public void SendText_Should_Send_a_Text()
{
var config = GetTestConfig();
var service = new NotificationService(config);
service.SendText(ValidToNumber, "Test message");
// Should complete w/o exception
}
To test an invalid phone number, use the magic number "+15005550001" (which we can assign to the constant InvalidNumber
):
[TestMethod]
[ExpectedException(typeof(NotificationException))]
public void SendText_Should_Throw_Exception_If_Bad_Number()
{
var config = GetTestConfig();
var service = new NotificationService(config);
service.SendText(InvalidNumber, "Test message");
}
Notice how this test uses the ExpectedException
attribute to ensure that an exception is raised when trying to send a text to an invalid number.
Test Making a Phone Call
Testing phone calls can be done using the same phone numbers. Any valid number will return a success response and "+15005550001" will raise an exception.
Test Buying a Phone Number
When buying a phone number, you can try to purchase "+15005550006" for a successful response and "+15005550000" to simulate trying to purchase an unavailable number.
Here's the full code for all our tests:
To download all of the code in a complete, working Visual Studio solution, head over to GitHub.
What's Next
There's a lot more you can do with test credentials, so do read up on them in our API Reference. Also, check out our C# TutorialsC# Tutorials. Most have tests written for them, like the Conference & Broadcast tutorial.
Until next time, happy testing.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.