I'm a big fan of TestCases because they make it trivial to test more edge cases, and the anti-testers seem happier with fewer lines of testing code. I've always struggled with exception testing though.
In my code I have a validation function that checks if there is 1 and only 1 instance in the config. If there are 0 I throw a KeyNotFoundException, and if there's more than 1 a AmbiguousMatchException.
private void ThrowIfNotInConfig(string endpointRef)
{
var count = _config.Endpoints?.Count(x => x.Ref.Equals(endpointRef)) ?? 0;
if (count == 0)
{
throw new KeyNotFoundException($"{nameof(ThrowIfNotInConfig)} - " +
$"No Endpoint in appSettings with the ref {endpointRef}");
}
if (count > 1)
{
throw new AmbiguousMatchException($"{nameof(ThrowIfNotInConfig)} - " +
$"More than 1 Endpoint in appSettings with the ref {endpointRef}");
}
}
Is there a way I can avoid splitting these into 2 separate tests, just because of the exception type, that's neater than this? I don't like doing a try catch in a test, and I feel there should be a way to test against the Exception, like ExpectedException used to.
[TestCase(0, ExpectedResult = "KeyNotFoundException")]
[TestCase(2, ExpectedResult = "AmbiguousMatchException")]
public string ThrowIfNotInConfig_GIVEN_NotASingleEndpointInConfig_THEN_ThrowError(int configOccurrences)
{
// Arrange
const string endpointRef = "ABC";
var config = _validConfig;
config.Endpoints.RemoveAll(x => x.Ref == endpointRef);
config.Endpoints
.AddRange(Enumerable.Range(0, configOccurrences)
.Select(x => new MiraklEndpointConfig { Ref = endpointRef })
);
_config.Setup(c => c.Value).Returns(config);
var service = new URLThrottlingService(_mockLogger.Object, _config.Object);
try
{
// Act
service.IsOKToCallEndpoint(endpointRef);
}
catch (Exception exception)
{
return exception.GetType().Name;
}
return "";
}
Thanks to canton7 and Olimasters answer, I now have this, which is what I was after
[TestCase(0, typeof(KeyNotFoundException))]
[TestCase(2, typeof(AmbiguousMatchException))]
public void ThrowIfNotInConfig_GIVEN_NotASingleEndpointInConfig_THEN_ThrowError(int configOccurrences, Type exception)
{
// Arrange
const string endpointRef = "ABC";
var config = _validConfig;
config.Endpoints.RemoveAll(x => x.Ref == endpointRef);
config.Endpoints
.AddRange(Enumerable.Range(0, configOccurrences)
.Select(x => new MiraklEndpointConfig { Ref = endpointRef })
);
_config.Setup(c => c.Value).Returns(config);
var service = new URLThrottlingService(_mockLogger.Object, _config.Object);
// Act / Assert
Assert.Throws(exception, () => service.IsOKToCallEndpoint(endpointRef));
}
Related
I have to unit test a function (SendMessageNetSkyToAgenceGrp), and inside this function there is a service that I want to mock (EnvoyerNotificationSms.SendMessageNetSky(signalRMessage)):
Here is the code i want to test :
public void SendMessageNetSkyToAgenceGrp(
int agenceId,
string message,
string name,
bool addAgenceLibInMsg = true,
SignalRMessageThemeEnum theme = SignalRMessageThemeEnum.Information)
{
Agence agence = AgenceCoreService.GetAgenceById(agenceId);
if (agence == null || (string.IsNullOrEmpty(agence.Libelle) && addAgenceLibInMsg))
{
return;
}
string finalMessage = (message + (addAgenceLibInMsg ? agence.Libelle : ""));
string groupName = string.Empty;
if (theme == SignalRMessageThemeEnum.NotificationSms)
{
groupName = SignalRConstantes.GRP_SMSAGENCE + SignalRConstantes.SEPARATOR + agenceId;
}
else
{
groupName = (SignalRConstantes.GRP_AGENCE + SignalRConstantes.SEPARATOR + agenceId);
}
SignalRMessage signalRMessage = new SignalRMessage(name, "", finalMessage, groupName, theme);
EnvoyerNotificationSms.SendMessageNetSky(signalRMessage);
}
And here is the test code:
[Fact(DisplayName = "Vérifier l'appel à l'infra IEnvoyerNotificationSms")]
public void SendMessageNetSkyToAgenceGrp_CasNormal_ResultatOk()
{
// Arange
var envoyerNotificationSmMock = new Mock<IEnvoyerNotificationSms>();
envoyerNotificationSmMock.Setup(envoyerNotifSms => envoyerNotifSms.SendMessageNetSky(It.IsAny<SignalRMessage>())).Verifiable();
var SignalRCoreService = LocalIocManager.Resolve<ISignalRCoreService>();
// Act
LocalIocManager.IocContainer.UseInstance(envoyerNotificationSmMock.Object, IfAlreadyRegistered.Replace);
SignalRCoreService.SendMessageNetSkyToAgenceGrp(56, "testMessage", "name", true, SignalRMessageThemeEnum.NotificationSms);
// Assert
envoyerNotificationSmMock.Verify(envoyerNotifSms => envoyerNotifSms.SendMessageNetSky(It.IsAny<SignalRMessage>()), Times.Once());
}
But when I execute the test I get an error telling me that the service I want to mock is null (EnvoyerNotificationSms.SendMessageNetSky(signalRMessage);)
The error is : 'Object reference not set to an instance of an object.' in the line EnvoyerNotificationSms.SendMessageNetSky(signalRMessage);
How can I solve this issue?
After checking the problem was the order of these lines :
var SignalRCoreService = LocalIocManager.Resolve<ISignalRCoreService>();
// Act
LocalIocManager.IocContainer.UseInstance(envoyerNotificationSmMock.Object, IfAlreadyRegistered.Replace);
In the second line has no effect, because the service i want to mock is already created in the first line. So the solution was to change the order of the two lines.
I have this method:
public void Handle(ShipmentConfirmedEvent message)
{
try
{
var trackingOrderDto = new ShipmentConfirmedDto
{
AccountId = message.AccountId,
FirstName = message.CustomerFirstName,
TransactionId = message.TransactionId,
Language = message.LanguageCode,
ExcludedCommunicationChannels = message.ExcludedCommunicationChannels,
TrackingId = message.TrackingNumber
};
Log.Info("Received notify of Shipment Confirmed request with id <{0}> for order {1}", message.GenerationId, message.TransactionId);
if (!string.IsNullOrEmpty(_iosForcePushAccountId))
{
Log.Info("Per device Ios, l'accountId {0} verrà forzato a {1} per configurazione", trackingOrderDto.AccountId, _iosForcePushAccountId);
trackingOrderDto.AccountId = Convert.ToInt64(_iosForcePushAccountId);
}
Log.Debug("Send push notification for IOS for AccountId <{0}>", trackingOrderDto.AccountId);
var iosContent = _sender.Create(trackingOrderDto, "IOS");
_sender.IosSend(iosContent);
trackingOrderDto.AccountId = message.AccountId;
if (!string.IsNullOrEmpty(_androidForcePushAccountId))
{
Log.Info("Per device Android, l'accountId {0} verrà forzato a {1} per configurazione", trackingOrderDto.AccountId, _androidForcePushAccountId);
trackingOrderDto.AccountId = Convert.ToInt64(_androidForcePushAccountId);
}
Log.Debug("Send push notification for Android for AccountId <{0}>", trackingOrderDto.AccountId);
var androidContent = _sender.Create(trackingOrderDto, "ANDROID");
_sender.AndroidSend(androidContent);
}
catch (ExcludedCommunicationChannelsException ex)
{
Log.Warn(ex.Message);
}
catch (Exception ex)
{
var msg = String.Format("Errore durante la gestione del messaggio relativo al PushNotification for username {0}", message.MailTo.First().Address);
Log.Error(ex, msg);
throw new Exception(ex.Message, ex);
}
Log.Info("<{0}> Processed", message.GenerationId);
}
and I have this test:
[Fact]
public void GivenAnOrderConfirmedEventWithForcedAccountId_WhenHandle_ThenCallSenderWithTheForcedAccountId()
{
NServiceBus.Testing.Test.Initialize();
var service = new ShipmentConfirmedRequestEventConsumer(mockSender, "1111", "2222");
NServiceBus.Testing.Test.Handler(svc => service)
.OnMessage<ShipmentConfirmedEvent>(
m =>
{
m.AccountId = 407716;
m.CustomerFirstName = "Marco";
m.TransactionId = "T123456";
m.LanguageCode = "IT";
m.TrackingNumber = "642167921";
}
);
mockSender.AssertWasCalled(x => x.Create(Arg<ShipmentConfirmedDto>.Matches(dto => dto.AccountId == 2222), Arg<string>.Is.Equal("ANDROID")));
mockSender.AssertWasCalled(x => x.Create(Arg<ShipmentConfirmedDto>.Matches(dto => dto.AccountId == 1111), Arg<string>.Is.Equal("IOS")));
}
the second AssertWasCalled, the one related to IOS, throw exception because it seems to be overwritten, being within the method the first to be called.
I need to verify that the Create method is called twice within the method, with the parameters specified.
How can I modify the test in order to verify it?
The solution is to modify test this way:
[Fact]
public void GivenAnOrderConfirmedEventWithForcedAccountId_WhenHandle_ThenCallSenderWithTheForcedAccountId()
{
NServiceBus.Testing.Test.Initialize();
mockSender.Expect(x => x.Create(Arg<ShipmentConfirmedDto>.Matches(dto => dto.AccountId == 2222), Arg<string>.Is.Equal("ANDROID"))).Return(string.Empty);
mockSender.Expect(x => x.Create(Arg<ShipmentConfirmedDto>.Matches(dto => dto.AccountId == 1111), Arg<string>.Is.Equal("IOS"))).Return(string.Empty);
var service = new ShipmentConfirmedRequestEventConsumer(mockSender, "1111", "2222");
NServiceBus.Testing.Test.Handler(svc => service)
.OnMessage<ShipmentConfirmedEvent>(
m =>
{
m.AccountId = 407716;
m.CustomerFirstName = "Marco";
m.TransactionId = "T123456";
m.LanguageCode = "IT";
m.TrackingNumber = "642167921";
}
);
mockSender.VerifyAllExpectations();
}
I am writing an http post call inside a for loop with typescript.
While debugging the method in the backend, I noticed that he treats the requests simultaneously.
For example, let's say the server must run 2 methods M1() then M2() per single request. For the test case with n = 2. The server executes M1() for request 1, M1() for request 2, then M2() for request 2 and finally M2() for request 2.
Then after the _session.commit() an exception is thrown in the method intercept. the exception description is:
NHibernate.StaleObjectStateException: 'Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Server.Model.Identity.ApplicationRole#3]'
Code:
public calculate(index?: number): void {
for (var i = 0; i < this.policy.coverages.length; i++) {
this.callCalculate(i);
}
}
public callCalculate(i: number): void {
this.premiumCalculationParams = this.policy.createPremiumCalculationParams();
if (!this.premiums) {
this.premiums = new Array(this.policy.coverages.length);
}
this.offerService.calculatePremiums(this.policy, i).then((result: any) => {
this.premiums[i] = new Array<Premium>();
this.surpremiums = new Array<PremiumResult>();
if (result.data && result.data.premiumTable && result.data.premiumTable.premiumPayment && result.data.premiumTable.premiumPayment.premiums && result.data.premiumTable.premiumPayment.premiums.length > 0) {
_.each(result.data.premiumTable.premiumPayment.premiums, (premiumValue: any) => {
let premium: Premium = new Premium();
premium.setPremium(premiumValue);
this.premiums[i].push(premium);
this.policy.getCoverage(i).premiumPayment.premiums = angular.copy(this.premiums[i]);
});
if (result.data && result.data.results && result.data.results.length > 0) {
_.each(result.data.results, (premiumValuel: any) => {
let sp = new PremiumResult();
sp.setPremiumResult(premiumValuel);
sp.premiums = new Array<Premium>();
_.each(premiumValuel.premiums, (premiumValue: any) => {
let premium: Premium = new Premium();
premium.setPremium(premiumValue);
sp.premiums.push(premium);
});
this.surpremiums.push(sp);
});
}
console.log(this.surpremiums);
}
if (result.data && result.data.premiumTable && result.data.premiumTable.messageList && result.data.premiumTable.messageList.messages && result.data.premiumTable.messageList.messages.length > 0) {
_.each(result.data.premiumTable.messageList.messages, (message: any) => {
let messageType: any = MessageType[message.messageLevel.toString()];
this.messages.push(new Message(messageType, "premiums", message.messageContent, this.premiums[i]));
});
}
}, (err: any) => {
this.premiums[i] = null;
this.surpremiums = null;
if (err && err.data && err.data.modelState) {
for (var key in err.data.modelState) {
var model = err.data.modelState[key];
_.each(model, (state: string) => {
this.$log.debug(OfferControllerPrefix, "Calculation failed: " + state);
});
}
}
this.messages.push(new Message(MessageType.SVR_ERROR, "premiumCalculationParams", this.jsTranslations.getTranslation(Constants.DEFAULT_ERROR_NL), this.policy));
});
}
public calculatePremiums(policy: Policy, selectedCoverageIndex : number): any {
var uri = this.uriService.buildURI("Policy/Calculate");
var data = {
'policy': policy,
'selectedCoverageIndex': selectedCoverageIndex
};
return this.$http.post<any>(uri, data);
}
How to solve this issue ?
To chain two asynchronous operations, return values to their .then methods:
function M1(data) {
return $http.post(url1, data);
}
function M2(data) {
return $http.post(url2, data);
}
Chain the two functions like so:
function M1M2 () {
var promise = M1(data);
var promise2 = promise.then(function(data) {
var data2 = fn(data);
var m2Promise = M2(data2);
return m2Promise;
});
return promise2;
}
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
For more information, see AngularJS $q Service API Reference - chaining promises.
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);
}
}
}
I have the following code
public bool IsUnitAvailable()
{
this.isUnitAvailable = false;
if(isUnitAvailable == false)
{
var exception = new Exception("Unit Unavailable");
exception.Data.Add("Quotation","1234567");
exception.Data.Add("propertyDate", "2016-10-10"); this.GetElmahExtensionWrapper().LogToElmah(exception);
}
}
return this.isUnitAvailable;
}
and the following unit test.
[TestMethod]
public void WhenUnitIsNotAvailableExceptionShouldBeLoggedInElmahTest()
{
//Arrange
var iPricingServiceMock = new Mock<IPricingService>(MockBehavior.Strict);
iPricingServiceMock.Setup(
counter => counter.IsUnitAvailableOn(It.IsAny<Unit>(),It.IsAny<DateTime>())).Returns(false);
var mockElmahExtensionWrapper = TestHelper.mk.GetMock<IElmahExtensionWrapper>();
// act
var quotation = new Quotation();
quotation.SetElmahExtensionWrapper(mockElmahExtensionWrapper.Object);
quotation.IsUnitAvailable();
//assert
mockElmahExtensionWrapper.Verify(counter => counter.LogToElmah(It.IsAny<Exception>()), Times.Exactly(1));
//change the test to verify that the exception that was logged had 2 Data properties?
}
The unit test is working. How can I change the test to verify that the exception that was logged had 2 Data properties? Changing the code to the following throws a "Cannot resolve symbol Data property" error.
mockElmahExtensionWrapper.Verify
(
counter => counter.LogToElmah
(
It.IsAny<Exception>(ex=>ex.Data.Count == 2)
),
Times.Exactly(1)
);
Change the verification to something like:
mockElmahExtensionWrapper.Verify(counter => counter.LogToElmah(It.Is<TraceException>(ex => ex.Data["Quotation"] == "1234567" && ex.Data["propertyDate"] == "2016-10-10"), Times.Exactly(1));