Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a method with an out parameter?

I am using a library that uses out parameters in a function and I need to test my code using that function.

So, attempting to have mocks come to my rescue here, via Moq which I've been using in the rest of the project.

Question

I know there's a wall of text below, so the question (in advance) is:

  • Per the lead below: Can Moq mock an item with a constructor that requires parameters that isn't normally called on its own?
  • Is this a problem with my test code? With the library? With the validation library?
  • Am I utilizing Moq with out parameters?
  • Where do I even start debugging this?

Update: Leads so Far

I'm thinking this is an issue on the mocking side with mocking the IXLRow interface. Normally it appears an XLRow is only instantiated from a workbook and never through new XLRow() -- is that a factor?

The following test passes when (note: mocks):

   [Fact]     public void TryGetValueCanReturnTrueForVieldWithAnInteger_WhenAccessingFromRow()     {         var workbook = new XLWorkbook();         workbook.Worksheets.Add("TestWS");         var wb = workbook.Worksheet("TestWS");         wb.Cell("A1").Value = "12345";          // NOTE: Here we're referring to the row as part of an instantiated           //       workbook instead of Mocking it by itself         int output;         Assert.True(wb.Row(1).Cell("A").TryGetValue(out output));     } 

The Code

Snippet of the method that gets a mock of a valid object():

// ...other code that sets up other parts of the row correctly int isAnyInt = 0; //I don't care about this value, only the true/false  // set this to false to true to mimic a row being a legitimate integer mock.Setup(m => m.Cell("B").TryGetValue(out isAnyInt)).Returns(true); 

xUnit test that tests the happy path -- Gets a mock of a valid row and then ensures it passes validation. NOTE: This test passes.

    [Fact]     public void Validate_GivenValidRow_ReturnsValid()     {         var mockRow = TestHelper.GetMockValidInvoiceDetailsWorksheetRow();          var validationResult = new InvoiceDetailsWorksheetRowValidator().Validate(mockRow.Object);         Assert.True(validationResult.IsValid);     } 

an xUnit test (basically, "does the validator fail with a cell that isn't an integer?") NOTE: This test passes.

    [Fact]     public void Validate_GivenNonNumericClaimantID_ReturnsInvalid()     {         int outint = 0;          // Get a mock of a valid row         var mockRow = TestHelper.GetMockValidInvoiceDetailsWorksheetRow();          // change the TryGetValue result to false         mockRow.Setup(m => m.Cell("B").TryGetValue(out outint)).Returns(false);          var validationResult = new InvoiceDetailsWorksheetRowValidator().Validate(mockRow.Object);         Assert.False(validationResult.IsValid);         Assert.Equal("ClaimantID column value is not a number.", validationResult.Errors.First().ErrorMessage);     } 

The validator (using FluentValidation):

public class InvoiceDetailsWorksheetRowValidator : AbstractValidator<IXLRow> {     public InvoiceDetailsWorksheetRowValidator()     {         RuleFor(x => x.Cell("B"))             .Must(BeAnInt).WithMessage("ClaimantID column value is not a number.")             .OverridePropertyName("ClaimantIDColumn");      }      private bool BeAnInt(IXLCell cellToCheck)     {         int result;         var successful = cellToCheck.TryGetValue(out result);         return successful;     } } 

For reference, the method from the library:

    public Boolean TryGetValue<T>(out T value)     {         var currValue = Value;          if (currValue == null)         {             value = default(T);             return true;         }          bool b;         if (TryGetTimeSpanValue(out value, currValue, out b)) return b;          if (TryGetRichStringValue(out value)) return true;          if (TryGetStringValue(out value, currValue)) return true;          var strValue = currValue.ToString();         if (typeof(T) == typeof(bool)) return TryGetBasicValue<T, bool>(out value, strValue, bool.TryParse);         if (typeof(T) == typeof(sbyte)) return TryGetBasicValue<T, sbyte>(out value, strValue, sbyte.TryParse);         if (typeof(T) == typeof(byte)) return TryGetBasicValue<T, byte>(out value, strValue, byte.TryParse);         if (typeof(T) == typeof(short)) return TryGetBasicValue<T, short>(out value, strValue, short.TryParse);         if (typeof(T) == typeof(ushort)) return TryGetBasicValue<T, ushort>(out value, strValue, ushort.TryParse);         if (typeof(T) == typeof(int)) return TryGetBasicValue<T, int>(out value, strValue, int.TryParse);         if (typeof(T) == typeof(uint)) return TryGetBasicValue<T, uint>(out value, strValue, uint.TryParse);         if (typeof(T) == typeof(long)) return TryGetBasicValue<T, long>(out value, strValue, long.TryParse);         if (typeof(T) == typeof(ulong)) return TryGetBasicValue<T, ulong>(out value, strValue, ulong.TryParse);         if (typeof(T) == typeof(float)) return TryGetBasicValue<T, float>(out value, strValue, float.TryParse);         if (typeof(T) == typeof(double)) return TryGetBasicValue<T, double>(out value, strValue, double.TryParse);         if (typeof(T) == typeof(decimal)) return TryGetBasicValue<T, decimal>(out value, strValue, decimal.TryParse);          if (typeof(T) == typeof(XLHyperlink))         {             XLHyperlink tmp = GetHyperlink();             if (tmp != null)             {                 value = (T)Convert.ChangeType(tmp, typeof(T));                 return true;             }              value = default(T);             return false;         }          try         {             value = (T)Convert.ChangeType(currValue, typeof(T));             return true;         }         catch         {             value = default(T);             return false;         }     } 

The Problem

The first test passes. But when I run this test, it fails:

   [Fact]    public void Validate_GivenNonNumericInvoiceNumber_ReturnsInvalid()     {         int outint = 0; // I don't care about this value          // Get a mock of a valid worksheet row         var mockRow = TestHelper.GetMockValidInvoiceDetailsWorksheetRow();          mockRow.Setup(m => m.Cell("E").TryGetValue(out outint)).Returns(false);          // Validates & asserts         var validationResult = new InvoiceDetailsWorksheetRowValidator().Validate(mockRow.Object);         Assert.False(validationResult.IsValid);          // Placed here to ensure it's the only error message. This is where it fails.         Assert.Equal("InvoiceNumber column value is not a number.",validationResult.Errors.First().ErrorMessage);     } 

But it doesn't fail because the validation hasn't been implemented -- it fails because the other item is invalid first, even though I'm returning it from getting a valid mock -- the same valid mock that passes tests otherwise.

The message, exactly, is:

Assert.Equal() Failure

Position: First difference is at position 0

Expected: InvoiceNumber column value is not a number.

Actual: ClaimantID column value is not a number.

I would expect:

  • It to work the same way the other test worked, or
  • For the happy path to also fail.

But when the happy path (e.g. valid mock) passes, but the test fails because the method is invalid (the same one that passes the same validation as part of the "valid" mock)...it leaves me completely confused.

For Reference

  • The library I'm using is ClosedXML
  • The validation library I'm using is FluentValidation
  • I'm using xUnit.NET to unit test.
like image 988
SeanKilleen Avatar asked Oct 02 '13 15:10

SeanKilleen


People also ask

How do you do mocks with out parameters?

Mocking this using Moq is pretty straightforward: _mockSub = "subword"; _testOrthoepedia = new Mock<IOrthoepedia>(MockBehavior. Strict); _testOrthoepedia .

How do you mock a method in Moq?

First, we instantiate the FakeDbArticleMock class and indicate which setup we want to use for this test. Then, it is necessary to instantiate the repository we want to test and inject the mock instance into it. Finally, we call the method we are testing and assert the results.

What can be mocked with Moq?

You can use Moq to create mock objects that simulate or mimic a real object. Moq can be used to mock both classes and interfaces. However, there are a few limitations you should be aware of. The classes to be mocked can't be static or sealed, and the method being mocked should be marked as virtual.


1 Answers

I don´t think you need to test TryGetValue In the library.

Put the BeAnInt in a separate class say XLCellHelpers, test this using a mock of IXLCell

Create an interface for XLCellHelpers, say IXLCellHelpers, inject that into your validator: InvoiceDetailsWorksheetRowValidator

Mock IXLCellHelpers to test the validator.

Like this:

using System;                      public class InvoiceDetailsWorksheetRowValidator : AbstractValidator<IXLRow> { private readonly IXlCellHelpers xlCellHelpers;      InvoiceDetailsWorksheetRowValidator(IXlCellHelpers xlCellHelpers)     {         this.xlCellHelpers = xlCellHelpers;     }          public InvoiceDetailsWorksheetRowValidator()     {         RuleFor(x => x.Cell("B"))             .Must(this.xlCellHelpers.BeAnInt).WithMessage("ClaimantID column value is not a number.")             .OverridePropertyName("ClaimantIDColumn");     } }  public interface IXlCellHelpers {     bool BeAnInt(IXLCell cellToCheck); }  public class XlCellHelpers : IXlCellHelpers {        publi bool BeAnInt(IXLCell cellToCheck)     {         int result;         var successful = cellToCheck.TryGetValue(out result);         return successful;     } } 
like image 70
klaasjan69 Avatar answered Oct 05 '22 23:10

klaasjan69