I'm trying to write some unit test around AWS DynamoDb C# library, CreateBatchWrite.
This is the method:
public void BatchSave<T>(string tableName, List<T> items)
{
if (string.IsNullOrWhiteSpace(tableName))
{
throw new ArgumentNullException(nameof(tableName));
}
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
items.RemoveAll(x => x == null);
BatchWrite<T> batch = _dynamoDbContext.CreateBatchWrite<T>(
new DynamoDBOperationConfig()
{
OverrideTableName = tableName
});
batch.AddPutItems(items);
batch.Execute();
}
What I want to test are my precondition
_dynamoDbContext is injected and it's an interface (IDynamoDBContext)
My problem is the return object from CreateBatchWrite: BatchWrite is a strongly typed class.
Apart from moving my preconditions one layer below or above, is there any way of unit testing this? Is my approach correct?
If you only want to test preconditions then create an instance of the class under test, pass arguments that will cause the expected behavior and assert that it happens as expected.
There is no need to even mock the dependency if it is not needed for the test to be exercised to completion.
For example
[TestClass]
public class AwsTests {
[Test]
public void Should_Throw_For_Null_TableName() {
//Arrange
var subject = new SubjectUnderTest(null);
ArgumentNullException exception = null;
var expected = "tableName";
//Act
try {
subject.BatchSave<object>(null, null);
} catch (ArgumentNullException e) {
exception = e;
}
//Assert
exception.Should().NotBeNull();
exception.ParamName.Should().Be(expected);
}
[Test]
public void Should_Throw_For_Null_Items() {
//Arrange
var subject = new SubjectUnderTest(null);
ArgumentNullException exception = null;
var expected = "items";
//Act
try {
subject.BatchSave<object>("fakeTableName", null);
} catch (ArgumentNullException e) {
exception = e;
}
//Assert
exception.Should().NotBeNull();
exception.ParamName.Should().Be(expected);
}
}
The above tests the two if conditions of the method as isolation unit tests by providing only what is necessary for the test to be safely exercised to completion.
Related
Moq doesn't match the mocked method.
Exception:
Exception thrown: 'Moq.MockException' in Moq.dll:
'IMongoRepository.FindByVrcId("b4cb3139-90aa-4477-979b-d893e3317386")
invocation failed with mock behavior Strict. All invocations on the
mock must have a corresponding setup.'
This is my unit test:
public class OfferHandlerTest : TestBase
{
Mock<IMongoRepository> repositoryMock = new Mock<IMongoRepository>(MockBehavior.Strict);
OfferHandler? offerHandler;
[Fact]
public void HandleTest()
{
JObject? offerFullDocument = null;
using (var sr = new StreamReader("Data/offer_full_document.json"))
{
var reader = new JsonTextReader(sr);
offerFullDocument = JObject.Load(reader);
}
var kafkaPayload = new KafkaMessagePayloadSourceConnector();
kafkaPayload.OperationType = Constants.Mongo_OperationType_Update;
kafkaPayload.FullDocument = offerFullDocument;
OfferService service = new OfferService(repositoryMock.Object);
offerHandler = new OfferHandler(service, this.ServiceConfiguration);
offerHandler.Handle(kafkaPayload);
DHOffer offer = new DHOffer();
offer.Version = 1;
// THIS SETUP FAILS
repositoryMock.Setup(s => s.FindByVrcId<DHOffer>(It.IsAny<string>())).Returns(It.IsAny<DHOffer>());
repositoryMock.Verify(s => s.FindAndUpdate<DHOffer>(It.IsAny<DHOffer>()), Times.Once);
}
}
This is the handle method:
public void Handle(KafkaMessagePayloadSourceConnector kafkaPayload)
{
VRCOffer offer = kafkaPayload!.FullDocument!.ToObject<VRCOffer>()!;
if (kafkaPayload!.OperationType!.Equals(Constants.Mongo_OperationType_Update))
{
offerService.updateOfferStatus(OfferMapper.MapToDataHubModel(offer), offer.MasterId);
}
}
And finally the service method:
public class OfferService
{
private readonly IMongoRepository offerRepository;
public OfferService(IMongoRepository offerRepository)
{
this.offerRepository = offerRepository;
}
internal void updateOfferStatus(DHOffer offer, string vrcMasterId)
{
// THIS SHOULD RETURN MOCKED OBJECT
DHOffer existingOffer = offerRepository.FindByVrcId<DHOffer>(vrcMasterId);
existingOffer.ModifiedDate = DateTime.Now.ToString();
existingOffer.AdditionalInformation.Status = offer?.AdditionalInformation?.Status!;
offerRepository.FindAndUpdate(existingOffer);
}
}
I tried using It.IsAny<DHOffer>() in the Return() method but I get the same exception.
Your issue is that you are running the Moq setup after the mocked object has been used. By then it's already too late. You just need to run the .Setup(...) commands at the start.
It's also worth noting that using a shared mock can be problematic if multiple tests need the setups to be different. Some tools can run tests in parallel which may screw things up, or in a different order which can cause things to be more brittle.
I'm working on a legacy project which is created with .NET Framework 4.8, locator pattern, and business factory to instantiate businesses.
I'm trying to add unit tests using MSTest and MOQ.
The SubmitEntities method in mocked business does not run validations. I mean when I run a test that is expected to throw an exception, it passes successfully. I have no idea why is this happening.
Also, I don't have experience in writing unit tests and this is my first time.
In short, I'm expecting to get exception when the test is run, but it finishes with submittedBalanceMappings with a count of more than zero.
The test class constructor is:
private readonly BalanceMappingBusinessService _balanceMappingBusinessService;
private readonly Mock<IBalanceMappingBusiness> _mockBalanceMappingBusiness = new Mock<IBalanceMappingBusiness>();
public BalanceMapping()
{
_balanceMappingBusinessService = new BalanceMappingBusinessService(_mockBalanceMappingBusiness.Object);
}
The test is:
[TestMethod]
public async Task SubmitEntities_NoDataWillBeSubmitted_IfColumnSettingIdDoesNotExist()
{
// Arrange
await LoginWithSuperAdminAccount();
var submittingBalanceMappings = new List<BalanceMappingEntity>
{
new BalanceMappingEntity
{
AdjustedBalanceDetailId = adjutedBalanceDetailId,
ColumnSettingId = Guid.NewGuid(),
EntityState = EntityState.Added
}
};
var submittedBalanceMappings = new List<BalanceMappingEntity>();
_mockBalanceMappingBusiness.Setup(
bmb => bmb.SubmitEntities(submittingBalanceMappings))
.Callback((List<BalanceMappingEntity> balanceMappings) => submittedBalanceMappings.AddRange(balanceMappings));
var adjutedBalanceDetailId = GetAdjustedBalanceDetail();
//Act
_balanceMappingBusinessService.SubmitEntities(submittingBalanceMappings );
// Assert
Assert.IsTrue(submittedBalanceMappings.Count == 0);
}
The SubmitEntities method is:
public void SubmitEntities(List<BalanceMappingEntity> balanceMappings)
{
try
{
ValidateSubmittingBalanceMappings(balanceMappings);
using var onlineUnitOfWork = new OnlineUnitOfWork();
foreach (var balanceMapping in balanceMappings)
{
balanceMapping.AccountId = ReusableObjects.Current.CurrentAuthenticatedAccountId;
onlineUnitOfWork.Submit(balanceMapping);
}
onlineUnitOfWork.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
}
I am testing my viewModel which contains a method that comes from a dependency injection, but does not enter the method, I honestly do not know what I am doing wrong.
my test.
[TestCase("5")]
[TestCase("10")]
[TestCase("-1")]
[TestCase("0")]
[TestCase("100")]
//[TearDown]
[Test]
public async Task Deve_Obter_A_Lista_De_Herois_Dado_A_Quantidade_Menor_Ou_Igual_A_100_E_Maior_Que_Zero(string quantidadeHerois)
{
//Arrang
var mockRepo = new Mock<IHeroes>();
ListHeroesViewViewModel listHeroesViewViewModel = new ListHeroesViewViewModel(null, mockRepo.Object);
//action
await listHeroesViewViewModel.GetHeroes(quantidadeHerois);
//assert
if(quantidadeHerois.Equals("5"))
Assert.AreEqual(5, listHeroesViewViewModel.Herois.Count);
else if (quantidadeHerois.Equals("10"))
Assert.AreEqual(10, listHeroesViewViewModel.Herois.Count);
else
Assert.AreEqual(100, listHeroesViewViewModel.Herois.Count);
}
my viewmodel
public async Task GetHeroes(string limit)
{
try
{
IsBusy = true;
///
////in this point it does not enter the method and returns null////
///
var heroes = await _heroes.GetHeroes(limit);
if (heroes.data != null)
Herois = new ObservableCollection<Result>(heroes.data.results);
}
catch (Exception ex)
{
await App.Current.MainPage.DisplayAlert("Atenção", $"Error:{ex.Message}", "Ok");
}
finally
{
IsBusy = false;
}
}
constructor viewModel
public ListHeroesViewViewModel(INavigationService navigationService, IHeroes heroes) : base(navigationService)
{
_navigation = navigationService;
//
///here he receives the dependency caused by mock.object
//
_heroes = heroes;
Title = "Heroes";
}
I don't know what I could be doing wrong, thank you for understanding this is my first test
By default a mock will return default values, which means for class types you get null.
If you want to return something you need to set it up.
Here is an example of a setup that is saying whenever something calls method GetHeroes return new Hero().
var mockRepo = new Mock<IHeroes>();
mockRepo.Setup(x => x.GetHeroes(It.IsAny<string>())).Returns(new Hero());
This is a simple setup, you can make it as complex as you want.
You can find more info here on how mocking works.
I have such service as below. Let's say i want to test Create() method. I read that in unit testing i should test by comparing, counting and so on. How could i then test my Create() method. Is it ugly to change return type from void Create to bool Create just to be able to check method output for testing purpose or that's not ideal idea? Can you propose something?
public class CreateCarService : ICreateCarService
{
private readonly ICarQuery _carQuery;
private readonly ICarRepository _carRepository;
public CreateCarService(ICarQuery carQuery, ICarRepository carRepository)
{
_carQuery = carQuery;
_carRepository = carRepository;
}
public void Create(Car car)
{
if (car == null) throw new CusException(Error, "Car object cannot be null");
if (_carQuery.IsLoginExist(car.Login))
throw new CusException(Error, "Message1");
if (_carQuery.IsEmailExist(car.Email))
throw new CusException(Error, "Message1");
_carRepository.Add(car);
}
}
You might verify that for any valid Car instance Add method was called only once, by setting up the Moq behavior of IsLoginExist and IsEmailExist method an using a Verify method
[TestFixture]
public class Test
{
[Test]
public void CreateCarServiceTest()
{
var carQueryMock = new Mock<ICarQuery>();
var carRepositoryMock = new Mock<ICarRepository>();
var createCarService = new CreateCarService(carQueryMock.Object, carRepositoryMock.Object);
carQueryMock.Setup(c => c.IsLoginExist(It.IsAny<string>())).Returns(false);
carQueryMock.Setup(c => c.IsEmailExist(It.IsAny<string>())).Returns(false);
createCarService.Create(new Car());
carRepositoryMock.Verify(c => c.Add(It.IsAny<Car>()), Times.Once);
}
}
It also makes sense to check a negative cases, when Create method throws an exception
[Test]
public void CreateCarNegativeTest()
{
var carQueryMock = new Mock<ICarQuery>();
var carRepositoryMock = new Mock<ICarRepository>();
var createCarService = new CreateCarService(carQueryMock.Object, carRepositoryMock.Object);
Assert.Throws<CusException>(() => createCarService.Create(null));
carQueryMock.Setup(c => c.IsLoginExist(It.IsAny<string>())).Returns(true);
Assert.Throws<CusException>(() => createCarService.Create(new Car()));
carQueryMock.Setup(c => c.IsLoginExist(It.IsAny<string>())).Returns(false);
carQueryMock.Setup(c => c.IsEmailExist(It.IsAny<string>())).Returns(true);
Assert.Throws<CusException>(() => createCarService.Create(new Car()));
}
You can split this method into different tests to have one Assert per test, or pass the parameters to it.
You want to test the "expected behavior" of the member under test. Since the member under test does not return any verifiable output and has a dependency on external abstractions, you should be able to monitor the interaction of the member under test with that external abstractions and verify the expected behavior
One such example
public void CreateCarService_Create_Should_Add_Car() {
//Arrange
Car car = new Car {
Login = "Login",
Email = "Email"
};
ICarQuery carQuery = Mock.Of<ICarQuery>();
ICarRepository carRepository = Mock.Of<ICarRepository>();
ICreateCarService subject = new CreateCarService(carQuery, carRepository);
//Act
subject.Create(car);
//Assert
Mock.Get(carRepository).Verify(_ => _.Add(car), Times.Once);
}
The example above safely navigates through to the end of the member under test but lets say you wanted to test the exception is thrown for the null parameter case.
public void CreateCarService_Create_Should_Throw_CusException_For_Null_Car() {
//Arrange
ICreateCarService subject = new CreateCarService(null, null);
//Act
Action act = ()=> subject.Create(null);
//Assert
var ex = Assert.Throws<CusException>(act);
}
You want to create tests for all the possible paths through the member under test. So take some time and review the subject under test and work out the possible test cases. Arrange the subject to satisfy those cases and exercise those cases to verify the expected behavior.
Reference Moq Quickstart to get a better understanding of how to use the Moq mocking framework.
You don't need to change it to bool, just to test. A simple way you can do this is:
[TestFixture]
public class Test
{
CreateCarService createCarService;
ICarRepository carRepositoryMock;
[Setup]
public void InitializeTest()
{
var carQueryMock = new Mock<ICarQuery>();
carRepositoryMock = new Mock<ICarRepository>();
createCarService = new CreateCarService(carQueryMock.Object, carRepositoryMock.Object);
}
[Test]
public void CreateCarShouldThrowIfNull()
{
//arrange
Car car =null;
//act and assert
Assert.Throw<CustomException>(()=>
{
createCarService.CreateCar(car);
});
}
[Test]
public void CreateCarShouldThrowForInvalidLogin()
{
//arrange
var car = new Car()
{
Login=null,
Email="Email"
};
//act and assert
Assert.Throw<CustomException>(()=>
{
createCarService.CreateCar(car);
});
}
And So on.
You can use Assert.Throw for invalid car objects or Assert.DoesNotThrow for valid car objects. Finally, you can test if the car was added to the repository by:
[Test]
public void CreateCarShouldAddCarToRepo()
{
//arrange
var car = new Car()
{
Login="Login",
Email="Email"
};
//act
createCarService.CreateCar(car);
var carRetrieved =carRepositoryMock.GetCar(car.id);//depending on your implementation
//assert
Assert.AreSame(car,carRetrieved);
}
So I know with TDD you're supposed to write tests first but I can't get my head around how to write a test for the following code. Can someone help me out with at starting point?
private string GetWMIProperty(string property)
{
string value = string.Empty;
SelectQuery selectQuery = new SelectQuery("Win32_OperatingSystem");
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(selectQuery))
{
foreach (ManagementObject mo in searcher.Get())
{
value = mo[property].ToString();
}
}
return value;
}
You'd just write tests for the method's various outcomes, and in doing so you'd define the method's expected behaviour without actually writing the method yet:
[TestMethod]
public MyClass_GetWMIProperty_GivenGoodInput_ReturnsString()
{
var myClass = new MyClass();
var result = myClass.GetWMIProperty("goodinput");
Assert.IsNotNull(result);
}
[TestMethod]
public MyClass_GetWMIProperty_GivenNullInput_ThrowsArgumentNullException()
{
var myClass = new MyClass();
try
{
var result = myClass.GetWMIProperty(null);
}
catch (ArgumentNullException)
{
// Good
return;
}
// Exception not thrown
Assert.Fail();
}
[TestMethod]
public MyClass_GetWMIProperty_GivenBadInput_ReturnsNull()
{
var myClass = new MyClass();
var result = myClass.GetWMIProperty("badinput");
Assert.IsNull(result);
}
Your method would be stubbed as follows:
// Note public/internal so tests can see it
public string GetWMIProperty(string property)
{
// Stubbed
throw new NotImplementedException();
}
These 3 test methods will fail in this state, because NotImplementedException will be thrown and not caught by any of them.
Next you'd write the actual meat of the method so that you can call it in these tests and they'd all pass. The core idea of TDD is that the tests define the behaviour. Here we have defined:
good input returns a string
bad input returns null
null input throws an ArgumentNullException.