A DSL for testing Income Tax Calculations

At ClearTax, being accurate is one of our most crucial requirements. We have to ensure that each and every tax return prepared by ClearTax is correct. As a result, we put a lot of emphasis on testing our tax-related code.

Testing income tax calculations is challenging:

  1. Test cases may be very detailed and complex. This is challenging because it makes adding test cases harder — introduces friction.
  2. You cannot test against your own calculations. Your assertions need to be independently verified — either by doing the calculations by hand, or against the tax department’s official implementation.
  3. You might need domain knowledge to figure out what the actual edge conditions are.

This problem was not easy to solve, but by the last tax season, we ended up with an approach that’s proven pretty successful.

Building a DSL

We ended up building an internal DSL for writing test cases.

test.WithSalary(600000)
.WithInterestIncome(100000)

.OnDate(2016, 10, 20)

.ShouldHave(I.IncomeTax, 65000);

This DSL ended up with three main types of statements:

  • With statements, which add data to a tax return
  • Should statements, which are assertions
  • Configuration statements: setting calculation date, etc (income tax calculations are time-dependent)

Adding a very complex test case is actually pretty easy now:

test.WithResidentialStatusNonResident()
.WithSalary(300000)
.WithSavingsInterest(200000)
.With80C(50000)
.WithTds(30000)
    .OnDate(2016, 6, 1)
    .ShouldHaveDeduction(D.Sec80TTA, 10000)
.ShouldHave(I.GrossTax, 19570)
.ShouldHave(I.Refund, 10430);

The tooling would give auto-complete and make it easy for us to add any types of test cases. This has drastically simplified the process of adding new test cases.

Programmatic Tests, Code Generation

We went further: the test framework would also keep track of which commands it has received. This means that you can replay actions taken, or serialise a test execution as code.

test.ToString()
> "test.WithSalary(600000).WithInterestIncome(100000).OnDate(2016, 10, 20).ShouldHave(I.IncomeTax, 65000);"

Finally, we wrote a wrapper on top of this test framework to translate the test case into a format that the government’s tools understand, and auto-generate assertions.

test.GenerateTestCase()
> "test.ShouldHave(I.IncomeTax, 65000).ShouldHave(I.EducationCess, 1950);"

As a result:

  • We can programatically generate test cases (using reflection or any other mechanism)
  • Automatically generate assertions against a neutral implementation
  • Convert these test cases into code, and add the concrete test cases to our test suite

The implications are huge. We can do exhaustive testing — just write code to generate all types of edge conditions instead of thinking about it yourself!


Using this approach, we have been able to make ClearTax much more robust. We are strong believers in building internal tools for our own use, building leverage for ourselves.

This DSL hides a lot of complexity within, but the end user only has to deal with a simple and logical interface. You don’t have to worry about the internal implementation, you can define a test case at a higher level. This helps us move faster, while ensuring we are always accurate :)


Interested in working with us? We’re hiring! Drop me a note on ankit@cleartax.in.