How would I write unit tests for something like this? - c#

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.

Related

Moq doesn't match methods. Moq.MockException: All invocations on the mock must have a corresponding setup

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.

Validation of submitEntities Method does not run when mocked at unit testing

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;
}
}

AWS CreateBatchWrite unit test

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.

Passing the null object from a test method

I have a class Class1, which has a constructor and few methods. For these methods I'm writing the unit test cases using MSTest. The class looks like this.
class Class1
{
Order order = new Order(); // there is an class Order
public Class1(Order _order)
{
order = _order;
}
public virtual async Task<bool> GetData()
{
if(order != null)
{
//do something
}
else
{
// do something else
}
}
}
Now, I have to write 2 test cases for this GetData() method, when which tests if block and one which tests the else block. I was able to test the if block successfully, but not able to test the else block. The test method which I'm trying to write is as below.
[TestMethod()]
public void GetDataTest()
{
Order order = new Order();
order = null;
var mockService = new Mock<Class1>(order)
{
CallBase = true
};
var result = await mockService.Object.GetData(); // error thrown from here
Assert.IsFalse(result);
}
What I'm trying to do is set the order object to null and pass the null object to the constructor.But this is throwing some error "Ambiguous match found". Clearly passing the null value is not working here. So can anyone tell me any other work around to test the else block.
PS: I need to test both if and else block so that it is included in the code coverage.
If your code is real then actually there is no need to mock service under test.
This works just fine:
[TestMethod]
public async Task GetDataTest()
{
//Arrange
Order order = null;
var c1 = new Class1(order);
//Act
var result = await c1.GetData();
//Assert
Assert.IsFalse(result);
}
Well, the unit test case and the code which you have shared doesn't have any issue
Except
You are not returning a task, and the error "ambiguous match found" looks like coming from inside the code which is written inside else block.
Try to change your GetData() method as :
public virtual async Task<bool> GetData()
{
TaskCompletionSource<bool> ts = new TaskCompletionSource<bool>();
if (order != null)
{
//do something
ts.SetResult(true);
}
else
{
// do something else
ts.SetResult(false);
}
return await ts.Task;
}
You must return from "GetData()"
public virtual async Task<bool> GetData()
{
if(order != null)
{
//do something
}
else
{
// do something else
}
//return task here with await keyword;
}

Should I unit test multiple (different) input values to a function?

Give the following function:
public class UnderTest
{
public bool Foo(Bar input)
{
if(input.State != State.Paid)
throw new Exception();
return true;
}
}
Whats the best way to test input.State != State.Paid given that State is an enum? I came up with the following. However, this will not catch if a new enum value is added. Is there a better way to test this or should I only care about a single test?
[Theory]
[InlineData(State.New)]
[InlineData(State.Cancelled)]
[InlineData(State.Complete)]
public void NotPaidBar_ThrowsException(State state)
{
// Arrange
var bar = new Bar()
{
State = state
};
var underTest = new UnderTest();
// Act
Action result = () => underTest.Foo(bar);
// Assert
result
.ShouldThrow<Exception>();
}
It is important to consider that unit-tests will not ensure that your program is correct, but only that it isn't broken as per your definitions.
As for your particular question, if you're using TDD with triangulation, and if you stumble upon a new test that doesn't force you to write any new production code, then I would feel that the extra test is not useful from a productivity standpoint.
You can test all the states with a simple loop if you want to, even those that would be added later to the enum:
public void NotPaidBar_ThrowsException()
{
var allStates = Enum.GetValues(typeof (State)).Cast<State>();
foreach (var state in allStates.Except(new[]{State.Paid}))
{
// Arrange
var bar = new Bar()
{
State = state
};
var underTest = new UnderTest();
// Act
Action result = () => underTest.Foo(bar);
// Assert
result.ShouldThrow<Exception>();
}
}

Categories

Resources