Noob question coming but I have a method that is included in my unit under test and I am checking that this method received the certain arg values that I declare in my arrange of the unit test but its failing on 2 of the first args.
How can simply check globalSettings and roomGuestRoom are the same that were passed passed into the SUT as in the received in the assert?
I tried using the Arg.Is on the globalSettings but neither worked nor simply passing roomGuestRoom directly as the same value?
Checkout my code below:
[Test]
public async Task ConfirmOrderClickCommand_StateUnderTest_ExpectedBehavior()
{
// Arrange
CartPageModel unitUnderTest = createCartPageModel();
Settings.GlobalSettings = new GlobalSettings()
{
Hotel = new Hotel()
{
Id = int.Parse(Constants.HotelId),
Name = Constants.HotelName
},
UserName = Constants.Username,
RoomNo = Constants.RoomNo,
Url = Constants.TestingUrl
};
await Settings.GlobalSettings.SetPasswordAsync(Constants.Password, _subSecureStorage).ConfigureAwait(true);
Settings.RoomGuestRoom = new RoomGuestRoom
{
FolioNo = 1234
};
_subConnectivity.ReturnsForAll(NetworkAccess.Internet);
_subUserDialogs.ConfirmAsync(new ConfirmConfig()).ReturnsForAnyArgs(Task.FromResult(true));
ObservableCollection<InHouseServiceViewModel> InCartInHouseServiceViewModels = new ObservableCollection<InHouseServiceViewModel>
{
new InHouseServiceViewModel(new InHouseService
{
Name="Inhouse service",
Comment= "Foo comment",
Price = 12,
})
{
Quantity = 2,
Comment = "Foo comment",
IndividualPrice = 12,
TotalPrice = 24,
PreferredDateTime = DateTime.Now
},
};
unitUnderTest.CartInHouseServicesViewModels = InCartInHouseServiceViewModels;
ResponseBaseModel ResponseBaseModel = new ResponseBaseModel
{
Status = "SUCCESS"
};
List<BookedInHouseService> bookedInHouseServices = unitUnderTest.CartInHouseServicesViewModels.Where(cartItem => cartItem.Quantity >= 1).Select(inhouseViewModel => new BookedInHouseService(inhouseViewModel.InHouseService.Id, inhouseViewModel.Comment, inhouseViewModel.PreferredDateTime)).ToList();
_subApiService.InsertRoomServiceBookingsRequestAsync(Settings.GlobalSettings, Settings.RoomGuestRoom, bookedInHouseServices).Returns(Task.FromResult(ResponseBaseModel));
// Act
unitUnderTest.ConfirmOrderClickCommand.Execute(null);
// Assert
await _subApiService.Received().InsertRoomServiceBookingsRequestAsync(Arg.Is<GlobalSettings>(globalSetting => globalSetting == Settings.GlobalSettings), Settings.RoomGuestRoom, Arg.Is<List<BookedInHouseService>>(bookedInHouseServices => bookedInHouseServices.Any(bookedInHouseService => InCartInHouseServiceViewModels.Any(inCartInHouseServiceViewModels => inCartInHouseServiceViewModels.InHouseService.Id == bookedInHouseService.Id)))).ConfigureAwait(false);
}
System under test is this:
public Command ConfirmOrderClickCommand => new Command(async () =>
{
if (_canClick)
{
_canClick = false;
if (Settings.GlobalSettings is GlobalSettings globalSettings && Settings.RoomGuestRoom is RoomGuestRoom roomGuestRoom && roomGuestRoom.FolioNo.HasValue)
{
if (CartInHouseServicesViewModels.Any(inHouseService => inHouseService.Quantity > 0))
{
bool placeOrderNow = await _userDialogs.ConfirmAsync(new ConfirmConfig
{
CancelText = AppResources!["LaterText"],
OkText = AppResources["NowText"],
Message = AppResources["PlaceOrderNowText"],
}).ConfigureAwait(true);
if (placeOrderNow)
{
using (_userDialogs.Loading(AppResources["PlacingOrderLoadingMessage"], null, null, true, MaskType.Black))
{
List<BookedInHouseService> bookedInHouseServices = CartInHouseServicesViewModels.Where(cartItem => cartItem.Quantity >= 1).Select(inhouseViewModel => new BookedInHouseService(inhouseViewModel.InHouseService.Id, inhouseViewModel.Comment, inhouseViewModel.PreferredDateTime)).ToList();
ResponseBaseModel? response = await Policy<ResponseBaseModel?>.Handle<Exception>().FallbackAsync(async cancellationToken => null, async result => ConsoleHelper.WriteLine(result.Exception, ConsoleColor.Red)).ExecuteAsync(async () => await _dataService.InsertRoomServiceBookingsRequestAsync(globalSettings, roomGuestRoom, bookedInHouseServices).ConfigureAwait(false)).ConfigureAwait(true);
if (response?.Success == true)
{
// Unit testing purposes, manually assign the navigation service name
if (Xamarin.Essentials.DeviceInfo.Platform == DevicePlatform.Unknown)
{
return;
}
await CoreMethods.PopToRoot(false).ConfigureAwait(false);
_userDialogs.Toast(response.Message);
}
}
}
}
else
{
_userDialogs.Toast(AppResources!["CartCannotBeEmptyText"]);
}
}
else if (Settings.GlobalSettings == null)
{
// Go admin settings page as global settings is null, unlikely to ever
// occur here.
App.MasterDetailNav!.NavigateToAdminSettingsPage();
}
else
{
// Go to welcome page as the folio no is null (no checked in guest)
App.MasterDetailNav!.SetMenuItem(Settings.GlobalSettings.Hotel!.Name);
}
_canClick = true;
}
});
The exception I am getting is the below:
Message:
NSubstitute.Exceptions.ReceivedCallsException : Expected to receive a call matching:
InsertRoomServiceBookingsRequestAsync(globalSetting => (globalSetting == Settings.GlobalSettings), RoomGuestRoom, bookedInHouseServices => bookedInHouseServices.Any(bookedInHouseService => value(XeniaRoomGuestApp.Tests.PageModels.CartPageModelTests+<>c__DisplayClass7_0).InCartInHouseServiceViewModels.Any(inCartInHouseServiceViewModels => (inCartInHouseServiceViewModels.InHouseService.Id == bookedInHouseService.Id))))
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
InsertRoomServiceBookingsRequestAsync(*GlobalSettings*, *RoomGuestRoom*, List<BookedInHouseService>)
Stack Trace:
ReceivedCallsExceptionThrower.Throw(ICallSpecification callSpecification, IEnumerable`1 matchingCalls, IEnumerable`1 nonMatchingCalls, Quantity requiredQuantity)
CheckReceivedCallsHandler.Handle(ICall call)
Route.Handle(ICall call)
CastleForwardingInterceptor.Intercept(IInvocation invocation)
AbstractInvocation.Proceed()
AbstractInvocation.Proceed()
ObjectProxy.InsertRoomServiceBookingsRequestAsync(GlobalSettings globalSettings, RoomGuestRoom roomGuestRoom, List`1 bookedInHouseServices)
<ConfirmOrderClickCommand_StateUnderTest_ExpectedBehavior>d__7.MoveNext() line 121
--- End of stack trace from previous location where exception was thrown ---
ExceptionDispatchInfo.Throw()
TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
GenericAdapter`1.GetResult() line 99
AsyncToSyncAdapter.Await(Func`1 invoke) line 60
TestMethodCommand.Execute(TestExecutionContext context) line 64
<>c__DisplayClass1_0.<Execute>b__0() line 58
BeforeAndAfterTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action) line 73
Also is it better for to change from ReturnsForAnyArgs to Returns if I am calling the Received on the method that was called in the SUT since I want to check on the args that were passed into the method? Correct me if wrong but Returns rather then ReturnsForAnyArgs enforces args to be provided to return the value so it would make sense to go Returns even though either would work?
Hi @LeoJHarris ,
Just having a quick look (can have a better look later, but thought I'd try to get you something to try in the meantime). If you use Arg.Any for the first two parameters does it pass?
await _subApiService.Received().InsertRoomServiceBookingsRequestAsync(
Arg.Any<GlobalSettings>(),
Arg.Any<RoomGuestRoom>(),
Arg.Is<List<BookedInHouseService>>(...))).ConfigureAwait(false);
If so it should be a matter of tracking down which object references are being passed to where. If you're using VS debugger you can create object ids which might help. You can also use _subApiService.ReceivedCalls() to manually inspect (in test, or via debugger) what arguments are being passed to each call. If Settings.GlobalSettings or Settings.RoomGuestRoom changes at all (from this, or from another parallel test) between the // Act and the // Assert then that will cause this test to fail.
Also, if you haven't done so already, please add NSubstitute.Analyzers to your test project. This can help pick up a lot of edge cases that can confuse NSubstitute.
Also is it better for to change from ReturnsForAnyArgs to Returns if I am calling the Received on the method that was called in the SUT since I want to check on the args that were passed into the method? Correct me if wrong but Returns rather then ReturnsForAnyArgs enforces args to be provided to return the value so it would make sense to go Returns even though either would work?
I think this depends on what you want to test. Yes, Returns will only return that value if the arguments passed match. If you are going to assert that the correct args have been passed during Received then maybe it makes sense to not also specify this when stubbing this call (so ReturnsForAnyArgs might be fine).
Another thing to consider is that if you can assert that the value from Returns was used correctly by the code under test, then you no longer have to check Received (as we know the call was received with the correct args, otherwise the value could not have been used correctly).
For this specific case then, it looks like you could use ReturnsForAnyArgs, then check that the call was Received as you are currently doing. In another test, use Returns and then check that CoreMethods.PopToRoot and _userDialogs.Toast were called when the response was successful. This second test may partially make the first test obsolete as you are implicitly testing the call has been received with the correct arguments, but you may want to keep the explicit test as a form of documentation (or to make an error in calling this more obvious from the test failure).
Hi @LeoJHarris ,
Just having a quick look (can have a better look later, but thought I'd try to get you something to try in the meantime). If you use
Arg.Anyfor the first two parameters does it pass?await _subApiService.Received().InsertRoomServiceBookingsRequestAsync( Arg.Any<GlobalSettings>(), Arg.Any<RoomGuestRoom>(), Arg.Is<List<BookedInHouseService>>(...))).ConfigureAwait(false);If so it should be a matter of tracking down which object references are being passed to where. If you're using VS debugger you can create object ids which might help.
Yes that did work.
Also, if you haven't done so already, please add NSubstitute.Analyzers to your test project. This can help pick up a lot of edge cases that can confuse NSubstitute.
Yup that was added already 馃憤
Another thing to consider is that if you can assert that the value from
Returnswas used correctly by the code under test, then you no longer have to checkReceived(as we know the call was received with the correct args, otherwise the value could not have been used correctly).
I see so essentially the Returns removes the need in my case to check the args later in received since if the value returned were correct then it assumed the args were infact correct in the implicit check. Is this check on the args values in Returns the same check as Received - both succeed if the values are same expected?
For this specific case then, it looks like you could use
ReturnsForAnyArgs, then check that the call wasReceivedas you are currently doing. In another test, useReturnsand then check thatCoreMethods.PopToRootand_userDialogs.Toastwere called when the response was successful. This second test may partially make the first test obsolete as you are implicitly testing the call has been received with the correct arguments, but you may want to keep the explicit test as a form of documentation (or to make an error in calling this more obvious from the test failure).
I'm leaning towards the use of Returns as then this would check the args on InsertRoomServiceBookingsRequestAsync and fail earlier if incorrect. I realised I may be abruptly finishing early but my issue was that CoreMethods was null and I have reached out on the project page how to correctly unit testing CoreMethods, otherwise I was hoping to check that PopToRoot was called.
Also to add I am mocking out the IApiService interface which includes the InsertRoomServiceBookingsRequestAsync such that this is unit tested as well so I think I doing this unit testing thing correctly so far (constructive criticism is always welcome 馃憤 ):
[Test]
public async Task InsertRoomServiceBookingsRequestAsync_StateUnderTest_ExpectedBehavior()
{
// Arrange
ApiService unitUnderTest = createService();
GlobalSettings globalSettings = new GlobalSettings()
{
Hotel = new Hotel()
{
Id = int.Parse(Constants.HotelId),
StripeId = Constants.StripeTestApiKey
},
UserName = Constants.Username,
UsingDemo = false,
Url = Constants.TestingUrl,
RoomNo = Constants.RoomNo,
};
RoomGuestRoom roomGuestRoom = new RoomGuestRoom()
{
RoomNo = Constants.RoomNo,
HotelId = int.Parse(Constants.HotelId),
FolioNo = Constants.FolioNo
};
List<InHouseServiceViewModel> InCartInHouseServiceViewModels = new List<InHouseServiceViewModel>
{
new InHouseServiceViewModel(new InHouseService
{
Name="Club Sandwich + Fries $10.00",
Comment= "Comment",
Id = 10000
}),
new InHouseServiceViewModel(new InHouseService
{
Name="vegetarian Salad $6.50",
Comment= "Comment",
Id = 10001
})
};
List<BookedInHouseService> bookedInHouseServices = InCartInHouseServiceViewModels.Where(cartItem => cartItem.Quantity >= 1).Select(inhouseViewModel => new BookedInHouseService(inhouseViewModel.InHouseService.Id, inhouseViewModel.Comment, inhouseViewModel.PreferredDateTime)).ToList();
// Act
ResponseBaseModel result = await unitUnderTest.InsertRoomServiceBookingsRequestAsync(globalSettings, roomGuestRoom, bookedInHouseServices).ConfigureAwait(false);
// Assert
Assert.True(result.Success);
}
If Arg.Any worked then it looks like the test is ok, so it seems that both the GlobalSettings and RoomGuestRoom references are genuinely different to the expected values. Please try inspecting _subApiService.ReceivedCalls() and see in what way the GlobalSettings reference is different from the one you are checking with Received. I think it is just going to be a hunt to find why those values have changed between acting and asserting.
If you can't track it down, please try and reduce the case to a standalone case you can share here. If I have some code to run to reproduce the problem then I may be able to help work out what's happening.
I see so essentially the Returns removes the need in my case to check the args later in received since if the value returned were correct then it assumed the args were infact correct in the implicit check. Is this check on the args values in Returns the same check as Received - both succeed if the values are same expected?
Sorry I think I was unclear with my explanation. Returns does not assert a particular call was received; only Received does that. What we can do is infer a particular call was received by whether the value from Returns was used (which implies that a call was received, rather than asserting it directly).
A rough example:
public class Example {
private ILookup lookup;
private IRepository repo;
// ...
public Change Update(int id) {
var findResult = lookup.Find(id);
return (findResult.Found) ? repo.Update(findResult.Item) : Change.None;
}
}
[Test]
public void TestExampleWhenFound() {
// Arrange
var lookup = Substitute.For<ILookup>();
var repo = Substitute.For<IRepository>();
var item = new Item();
var itemId = 42;
var change = new Change(itemId, item);
lookup.Find(itemId).Returns(new FindResult { Found = true, Item = item });
repo.Update(item).Returns(change);
var example = new Example(lookup, repo);
// Act
var result = example.Update(itemId);
// Assert
Assert.Same(change, result);
}
In this case, we never check repo.Received().Update(item), but we know it was called as that is the only way that result could be equal to change (I used Assert.Same to illustrate that it is the same reference, but Assert.Equal would be fine and I think is preferable.) If repo.Update was called with any other Item then it would produce a different value for change -- probably null for this case if it was not stubbed out. (You can try this out by updating the code or the argument used to stub and making sure the test fails.)
Similarly we also don't need to check lookup.Received.Find(itemId), as we know that the item returned from this call is used in the call to repo.Update(item).
Sorry if this is all a bit contrived. Please ask questions if I'm not being clear about this.
Also to add I am mocking out the IApiService interface which includes the InsertRoomServiceBookingsRequestAsync such that this is unit tested as well so I think I doing this unit testing thing correctly so far (constructive criticism is always welcome ...
This looks a very neat test! If I am going to try to find something constructive to add, maybe consider parameterising createService() so we can tell how the service is being stubbed out (assuming it is using mocks/stubs?).
@dtchepak
I checked on the GlobalSettings and RoomGuestRoom and using ReceivedCalls the method was being called and originalArgs and args look the same, regarding these two objects they are being stored in memory and in the process being Serialized and Deserialized would the below be giving objects to equal:
public static RoomGuestRoom? RoomGuestRoom
{
get
{
string flatObject = AppSettings.GetValueOrDefault(_roomGuestRoomKey, _settingsDefault);
return string.IsNullOrEmpty(flatObject) ? default : JsonConvert.DeserializeObject<RoomGuestRoom>(flatObject);
}
set
{
string flatObject = JsonConvert.SerializeObject(value);
AppSettings.AddOrUpdateValue(_roomGuestRoomKey, flatObject);
}
}
That all makes sense what your saying though and helps to me form a better understanding, appreciate the example code for the illistration! 馃憤
The createService() is as below, I also have a [SetUp] method that stubs:
[SetUp]
public void SetUp()
{
_subUserDialogs = Substitute.For<IUserDialogs>();
_subSecureStorage = Substitute.For<ISecureStorage>();
}
private ApiService createService()
{
return new ApiService(
_subUserDialogs,
_subSecureStorage);
}
This is a great library and many thanks for providing it 馃
From my tests as indicated above are these examples of mocking or stubbing or a combination of both? Novice question.
... being Serialized and Deserialized so maybe this is having some adverse effect.
Yes this could definitely be it! If Equals is not overridden for the type it will default to reference equality, which will be different after serialisation. One solution is to implement Equals for GlobalSettings and RoomGuestRoom; another is to put a separate equality test in the arg matcher such as Arg.Is<GlobalSettings>(x => MyCustomEquals(x, globalSettings)).
The createService() is as below ...
Looks great. 馃憤 Only other thing I can think of is that for a reader unfamiliar with the code (i.e. me :) ) it is not clear exactly what is being tested (the link between the data being passed in, what the role of the dependencies are, and the assertion). This is probably more obvious to someone that knows a bit about the code.
From my tests as indicated above are these examples of mocking or stubbing or a combination of both?
The term "mock" I think has become quite overloaded. "Mocking" used to be about setting expectations for calls and verifying these calls occurred. NSubstitute does not support expect/verify, instead we have Received as an alternative approach to this. The term "mocking" is also often used to mean anything to do with Test Doubles, so its meaning has probably been diluted quite a bit, and I'm not sure how useful the term is now.
I think "stubbing" is fairly consistently used to mean "making a test double return a specific value" (Returns in NSubstitute). That term is probably still useful.
Check out Martin Fowler's Mocks aren't stubs for more details.
As pioneered by Moq (AFAICT), NSubstitute intentionally blurs the line between mocks and stubs. Instead we just say we are "substituting" for something in a test (we don't mind if it's mocking, stubbing, faking, spying etc.). I still use the term "stubbing" to mean configuring a return value, but otherwise I try to avoid the term "mocking" and will talk more about "substituting for a type", "checking Received calls", etc.
In your test you aren't explicitly setting return values (just using the defaults returned) or asserting calls where received, so technically I'm not sure it counts as "mocking" or "stubbing". 馃し鈥嶁檪 I guess we can say you are "using test doubles". Or "substituting for dependencies".
@dtchepak thanks for feedback I think that gives me enough to go further on the original issue I was facing
In your test you aren't explicitly setting return values (just using the defaults returned) or asserting calls where received, so technically I'm not sure it counts as "mocking" or "stubbing". 馃し鈥嶁檪 I guess we can say you are "using test doubles". Or "substituting for dependencies".
Is this a bad thing? Should I be setting return values or asserting? Or does it just depend what I am trying to achieve and what the code is doing that is under test? Some unit testing is on Commands and there is no return value provided so I just check ViewModel state or other methods that were called in the unit under test.
Sorry for all the questions just wanting to clarify if I am taking the correct steps in my unit testing.
Thanks for that link Ill check it out.
Happy to try to help with unit testing questions! Just keep in mind that this is just my opinion. :)
I'm not sure it is bad. It definitely depends on what you're trying to achieve/test. Normally we need to do _something_ with dependencies in a test setup. For dependencies that get queried (i.e. use to get values from) then that will normally require stubbing. For dependencies called with a void member, it often means just checking the call was received (e.g. was the code meant to send an email? subEmailServer.Received().Send(....)).
For the case you mentioned, no return value but can check ViewModel state, these tend to be ideal. I try to skip substitutes/test doubles where ever possible, as ideally we want to test real code rather than test-versions of code. So providing some input and making sure the ViewModel gets set appropriately is pretty much the ideal case. In cases where it is harder to use real input/output (e.g. the email sending example, or if we want to simulate network calls with outages or similar), we can look at substituting for these dependencies.
just wanting to clarify if I am taking the correct steps in my unit testing.
I think this is tricky as there probably isn't such thing as "the correct steps". I still really struggle with this. To help, I keep reminding myself to focus on the question "what exactly am I trying to test?", and then "how can I do that?" (preferably as simply as I can). This probably sounds silly and trite, but it reminds me to focus less on mocks or libraries, and more on the overall aim of getting confidence that the specific thing I'm coding works.
That may involve substituting for a class that talks to a database, or standing up a test database, or manually creating another implementation that reads from the filesystem, or even deciding to manually test that particular thing. I also find it useful to test my test; change the code/introduce a deliberate bug and run the test to make sure it will pick up that problem (and report it in a useful way).
For your case, I would try to avoid questions like "Is this a bad thing?" or "Should I be setting return values or asserting?", and instead remind myself of what I'm try to achieve with this. Am I trying to test that calling InsertRoomServiceBookingsRequestAsync with a BookedInHouseService that is already booked out fails? How can I tell that is happening correctly?
Sorry for rambling; I just prefer talking about testing more than finishing loading the dishwasher. 馃槀
PS: just saw your edit to the comment to include the serialisation code.
public static RoomGuestRoom? RoomGuestRoom { get { ... } set { ... } }
This should be something we can test:
[Test]
public void RoundTrippingRoomGestRoomSetting() {
val original = new RoomGuestRoom(...);
GlobalSettings.RoomGuestRoom = original;
val roundTripped = GlobalSettings.RoomGuestRoom;
Assert.AreEqual(original, roundTripped);
}
@dtchepak will have a look back into this shortly, just got pulled with someother peice of work. Again thanks for the helpful advise/information and will let you know soon how that goes with what you suggest shortly.
@dtchepak Hi again,
I found some time to revit this, I like that RoundTripping test method thanks for suggestiing that :)
And got it working as well using as per your suggestion example:
Arg.Is<RoomGuestRoom>(roomGuestRoom =>roomGuestRoom.Equals(Settings.RoomGuestRoom))