How can I implement unit tests in big and complex classes?
First things, first - you should make the acquisition of this object done via an interface if the code there is currently pulling it from the DB. Then you can mock that interface to return whatever you want in your unit tests.
If I were in your shoes, I would extract the actual calculation logic and write tests towards that new "calculator" class(es). Break down everything as much as you can. If the input has a 100 properties but not all of them are relevant for each calculation - use interfaces to split them up. This will make the expected input visible, improving the code as well.
So in your case if your class is let us say named BigClass, you can create an interface that would be used in a certain calculation. This way you are not changing the existing class or the way the other code works with it. The extracted calculator logic would be independent, testable and the code - a lot simpler.
public class BigClass : ISet1
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
}
public interface ISet1
{
string Prop1 { get; set; }
string Prop2 { get; set; }
}
public interface ICalculator
{
CalculationResult Calculate(ISet1 input)
}
If those 100 values are not relevant and you need only some of them, then you have several options.
You can create new object (properties will be initialized with default values, like null
for strings and 0
for integers) and assign only required properties:
var obj = new HugeObject();
obj.Foo = 42;
obj.Bar = "banana";
You can also use some library like AutoFixture which will assign dummy values for all properties in your object:
var fixture = new Fixture();
var obj = fixture.Create<HugeObject>();
You can assign required properties manually, or you can use fixture builder
var obj = fixture.Build<HugeObject>()
.With(o => o.Foo, 42)
.With(o => o.Bar, "banana")
.Create();
Another useful library for same purpose is NBuilder
NOTE: If all properties are relevant to feature which you are testing and they should have specific values, then there is no library which will guess values required for your test. The only way is specifying test values manually. Though you can eliminate lot of work if you'll setup some default values before each test and just change what you need for particular test. I.e. create helper method(s) which will create object with predefined set of values:
private HugeObject CreateValidInvoice()
{
return new HugeObject {
Foo = 42,
Bar = "banaba",
//...
};
}
And then in your test just override some fields:
var obj = CreateValidInvoice();
obj.Bar = "apple";
// ...
For cases when I had to get a big amount of actual correct data for testing I've serialised the data into JSON and put that directly into my test classes. Original data can be taken from your database and then serialised. Something like this:
[Test]
public void MyTest()
{
// Arrange
var data = GetData();
// Act
... test your stuff
// Assert
.. verify your results
}
public MyBigViewModel GetData()
{
return JsonConvert.DeserializeObject<MyBigViewModel>(Data);
}
public const String Data = @"
{
'SelectedOcc': [29, 26, 27, 2, 1, 28],
'PossibleOcc': null,
'SelectedCat': [6, 2, 5, 7, 4, 1, 3, 8],
'PossibleCat': null,
'ModelName': 'c',
'ColumnsHeader': 'Header',
'RowsHeader': 'Rows'
// etc. etc.
}";
This may not be optimal when you have a lot of tests like this, as it takes quite a bit of time to get the data in this format. But this can give you a base data that you can modify for different tests after you are done with the serialisation.
To get this JSON you'll have to separately query the database for this big object, serialise it into JSON via JsonConvert.Serialise
and record this string into your source code - this bit relatively is easy, but takes some time because you need to do it manually... only once though.
I've successfully used this technique when I had to test report rendering and getting data from the DB was not a concern for the current test.
p.s. you'll need Newtonsoft.Json
package to use JsonConvert.DeserializeObject
Given the restrictions (bad code design and technical debt...I kid) A unit test will be very cumbersome to populate manually. A hybrid integration test would be needed where you would have to hit an actual datasource (not the one in production).
Potential potions
Make a copy of the database and populate only the tables/data needed to populate the dependent complex class. Hopefully the code is modularized enough that the data access should be able to get and populate the complex class.
Mock the data access and have it import the necessary data via an alternate source (flat file maybe? csv)
All other code could be focused on mocking any other dependencies needed to perform the unit test.
Barring that the only other option left is to populate the class manually.
On an aside, this has bad code smell all over it but that is outside of the scope of the OP given that it not able to be changed at this moment. I would suggest you make mention of this to the decision makers.