Search code examples
c#xunitautofixture

Unable to create a customized object using Auto Fixture in c# xunit


I have below class and a dictionary of category item mappings

public class InfoClass
    {
        public InfoClass()
        {
            this.InfoID = string.Empty;
            this.EDDetails = string.Empty;
            this.EVDetails = string.Empty;
        }
        public int ID { get; set;}
        public string InfoID { get; set;}
        public string EDDetails { get; set; }
        public string EVDetails { get; set; }
    }


    private static Dictionary<string, List<string>> ItemsMap = new Dictionary<string, List<string>>
            {
                { Constants.ITEMCATEGORY_ED, new List<string> { "XYZ", "IDASAS" } },
                { Constants.ITEMCATEGORY_ERANDRE, new List<string> { "SAS", "PQR" } }
            
            };

I want to create the InfoClass mock object in such a way that, the InfoID of InfoClass should be random from the list inside the Dictionary. Based on that category of the selected InfoID, assign a value for EDDetails or EVDetials.

For example, If the random value of ReportId is "XYZ", then category of InfoID is ITEMCATEGORY_ED, then assign {Generate data based on a logic} value to EDDetails property and all the other properties EVDetails should be empty.

To achieve this, i tried creating a Customization using below.

public class InfoClassCustomization : ICustomization
    {
        private readonly Dictionary<string, List<string>> ItemsMap;

        public ArchivedOrderLineItemInfoCustomization(Dictionary<string, List<string>> ItemsMap)
        {
            this.ItemsMap = ItemsMap;
        }

        public virtual void Customize(IFixture fixture)
        {
            fixture.Customize<InfoClass>(c => c
                .Do(o =>
                {
                    // Flatten your dictionary to a list of tuples
                    var keyValueList = ItemsMap.SelectMany(kvp => kvp.Value.Select(v => (Key: kvp.Key, Value: v))).ToList();

                    // Pick a random tuple
                    var InfoIdPair = keyValueList[new Random().Next(keyValueList.Count)];

                    // Set the ReportID from the randomly selected tuple
                    o.ReportID = InfoIdPair.Value;


                    // Set the correct property based on the key of the randomly selected tuple
                    switch (InfoIdPair.Key)
                    {
                        case Constants.ITEMCATEGORY_ED:
                            o.EDDetails = JsonConvert.SerializeObject(fixture.Create<EdDetails>());
                            break;
                        case Constants.ITEMCATEGORY_ERANDRE:
                            o.EVDetails = JsonConvert.SerializeObject(fixture.Create<EvDetails>());
                            break;
                    }
                }));
        }
    }



var fixture = new Fixture().Customize(new InfoClassCustomization(ItemsMap));

var obj = fixture.Create<InfoClass>();

But this didnt worked. Didn't even got any exception. So tried below

public static InfoClass GetItemInfo()
        {
            var fixture = new Fixture();
            InfoClass o = new InfoClass();
            // Flatten your dictionary to a list of tuples
                    var keyValueList = ItemsMap.SelectMany(kvp => kvp.Value.Select(v => (Key: kvp.Key, Value: v))).ToList();

                    // Pick a random tuple
                    var InfoIdPair = keyValueList[new Random().Next(keyValueList.Count)];

                    // Set the ReportID from the randomly selected tuple
                    o.ReportID = InfoIdPair.Value;


                    // Set the correct property based on the key of the randomly selected tuple
                    switch (InfoIdPair.Key)
                    {
                        case Constants.ITEMCATEGORY_ED:
                            o.EDDetails = JsonConvert.SerializeObject(fixture.Create<EdDetails>());
                            break;
                        case Constants.ITEMCATEGORY_ERANDRE:
                            o.EVDetails = JsonConvert.SerializeObject(fixture.Create<EvDetails>());
                            break;
                    }

            return o;
        }

var fixture = new Fixture();
            fixture.Customize<ArchivedOrderLineItemInfo>(c => c
            .Do(o => o= GetItemInfo()));

var mockedvalue = fixture.Create<InfoClass>();

With the second approach, while i debug, i can see the data/values assigned to the properties inside the GetItemInfo method. But once it returned the object and assigned to "mockedvalue", the data generated inside the method is gone and a fresh mock data with out the custom logic is generated.

Is there any issue with my logic ? or am i missing something ?


Solution

  • Thinking of it other way, more from unit test perspective, I have modified your solution with ICUstomization. Here is how I implemented it:

    public class InfoClassCustomization : ICustomization
    {
        private readonly Dictionary<string, List<string>> ItemsMap;
    
        public InfoClassCustomization(Dictionary<string, List<string>> ItemsMap)
        {
            this.ItemsMap = ItemsMap;
        }
    
        public void Customize(IFixture fixture)
        {
            // Flatten your dictionary to a list of tuples
            var keyValueList = ItemsMap.SelectMany(kvp => kvp.Value.Select(v => (Key: kvp.Key, Value: v))).ToList();
    
            // Pick a random tuple
            var InfoIdPair = keyValueList[new Random().Next(keyValueList.Count)];
    
            // Set the ReportID from the randomly selected tuple
            var reportID = InfoIdPair.Value;
            var edDetails = JsonConvert.SerializeObject(fixture.Create<EdDetails>());
            var evDetails = JsonConvert.SerializeObject(fixture.Create<EvDetails>());
    
            fixture.Customize<InfoClass>(c =>
                {
                    var composer = c.With(x => x.ReportID, reportID);
                    // Set the correct property based on the key of the randomly selected tuple
                    switch (InfoIdPair.Key)
                    {
                        case Constants.ITEMCATEGORY_ED:
                            return composer.With(x => x.EDDetails, edDetails);
                        case Constants.ITEMCATEGORY_ERANDRE:
                            return composer.With(x => x.EVDetails, evDetails);
                        default:
                            return c;
                    }
                });
        }
    }
    

    And here is sample unit test (I think you have missed call to Fixture's Customize(new InfoClassCustomization(dict));):

    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            // Arrange
            var dict = new Dictionary<string, List<string>>
            {
                { Constants.ITEMCATEGORY_ED, new List<string> { "XYZ", "IDASAS" } },
                { Constants.ITEMCATEGORY_ERANDRE, new List<string> { "SAS", "PQR" } }
            };
    
            var autoFixture = new Fixture()
                .Customize(new InfoClassCustomization(dict));
    
            // Act
            var infoClass = autoFixture.Create<InfoClass>();
    
            // Assert
            var possibleReportIds = dict.Values.SelectMany(x => x);
    
            Assert.NotNull(
                possibleReportIds
                    .FirstOrDefault(x => x == infoClass.ReportID));
        }
    }
    

    EDIT Accordingly to comments, above method will create all objects with the same ReportId. To correct that we can use Fixture.Register method, as below:

    public class InfoClassCustomization : ICustomization
    {
        private readonly Dictionary<string, List<string>> ItemsMap;
    
        public InfoClassCustomization(Dictionary<string, List<string>> ItemsMap)
        {
            this.ItemsMap = ItemsMap;
        }
    
        public void Customize(IFixture fixture)
        {
            // Flatten your dictionary to a list of tuples
            var keyValueList = ItemsMap.SelectMany(kvp => kvp.Value.Select(v => (Key: kvp.Key, Value: v))).ToList();
    
            fixture.Register(() =>
            {
                // Pick a random tuple
                var InfoIdPair = keyValueList[new Random().Next(keyValueList.Count)];
    
                // Set the ReportID from the randomly selected tuple
                var reportID = InfoIdPair.Value;
                var edDetails = "EDDetails";// JsonConvert.SerializeObject(fixture.Create<EdDetails>());
                var evDetails = "EVDetails";// JsonConvert.SerializeObject(fixture.Create<EvDetails>());
    
                return new InfoClass
                {
                    ReportID = reportID,
                    EDDetails = edDetails,
                    EVDetails = evDetails,
                    ID = fixture.Create<int>(),
                    InfoID = fixture.Create<string>(),
                };
            });
        }
    }
    

    Now below sample unit test passes:

    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            var dict = new Dictionary<string, List<string>>
            {
                { Constants.ITEMCATEGORY_ED, new List<string> { "XYZ", "IDASAS" } },
                { Constants.ITEMCATEGORY_ERANDRE, new List<string> { "SAS", "PQR" } }
            };
    
            var autoFixture = new Fixture()
                .Customize(new InfoClassCustomization(dict));
    
            var infoClasses = autoFixture.CreateMany<InfoClass>(10);
    
            var possibleReportIds = dict.Values.SelectMany(x => x).ToArray();
    
            foreach (var ic in infoClasses)
            {
                Assert.True(possibleReportIds.Contains(ic.ReportID));
            }
    
            Assert.True(infoClasses.Select(ic => ic.ReportID).Distinct().Count() > 1);
        }
    }