Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I implement unit tests in big and complex classes?

Tags:

I'm implementing unit tests in a finance system that involves several calculations. One of the methods, receives an object by parameter with more than 100 properties, and based on this object's properties, it calculates the return. In order to implement unit tests for this method, I need to have all of this object filled with valid values.

So...question: today this object is populated via database. On my Unit Tests (I'm using NUnit), I'd need to avoid the database and create a mock object of that, to test only the method's return. How can I efficiently test this method with this huge object? Do I really need to manually fill all of the 100 properties of it? Is there a way to automatize this object creation using Moq (for example)?

obs: I'm writing unit tests for a system that is already created. It's not feasible to re-write all the architecture at this moment.
Thanks a million!

like image 363
Lisa Shiphrah Avatar asked Feb 21 '17 09:02

Lisa Shiphrah


Video Answer


2 Answers

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

like image 42
trailmax Avatar answered Sep 22 '22 06:09

trailmax


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";  // ... 
like image 50
Sergey Berezovskiy Avatar answered Sep 25 '22 06:09

Sergey Berezovskiy