Thursday, July 21, 2011

Testing Rdlc Reports

[Download ApprovalTests]


Edit [4/25/2012] There is now a video on testing Rdlc Reports as well


Rdlc reports are a very common thing with many .Net projects. They are a convenient way to both show data, and produce printable pages. I have seen many of these when working with legacy code, and wanted to share how to test them.

Approval Tests offer a wide array of convenience methods to test rdlc reports. I’m going to build from the most common, to the most robust.

Scenario 1: Rdlc report, backed by a single dataset, all in the same project.

This is probably 95% of existing rdlc reports. In this example I have a project with rdlc report printing out the best insults from the insult database we use to teaching kids T-SQL at www.teachingkidsprogramming.org

It is all in a single project, the rdlc file is an embedded resource and then uses a Dataset called InsultsDataTable.

The great thing about testing rdlc reports is with approval tests is it’s still the single line you would expect....

Approvals.ApproveReport( reportName, reportData);

The report name is easy, it’s just a string for the location of the embedded rdlc file. The reportData might be a bit harder to recreate. Of course you can always dummy up the InsultsDataTable object by hand, if you are particular about the text of any given field, but I find often I am not, so we added a connivence extension method to DataTable to automatically add a dummy row to it. If you want multiple rows, just state how many. For example: .AddTestDataRows(42)

Here’s the Code

Approvals.ApproveReport("ReportingDemo.InsultsReport.rdlc"
                        new InsultsDataTable().AddTestDataRows());

when you do this, it will create render the rdlc report to a tiff file (I usually use the DiffReporter for viewing) and show you the output, simply approve it (I usually copy the move command from the console and run it in a command window).



Scenario 2: Being explicit and the datasource name

There was a lot of assumed knowledge in the previous step, let’s start with the name of the datasource. What if you want to state the name directly? Well, it’s basically the same code, except that you will now add the name

Approvals.ApproveReport( reportName, reportDatasourceName reportData);

Of course, who can remember those names ( I always try to fill in “Model” for mine to have uniformity, but then again we are talk about legacy code which is rarely uniform). What I usually do is pass in a blank string “”, and let the error message guide me.

Approvals.ApproveReport("ReportingDemo.InsultsReport.rdlc", ""
                        new InsultsDataTable().AddTestDataRows());


This will produce the following exception:

System.Exception : The Datasource Name ''
is not a legal match for ReportingDemo.InsultsReport.rdlc,
Legal Matches are: [Model]

See, this report datasource should have been “Model”. This is especially useful when there are multiple datasource (below).


Approvals.ApproveReport("ReportingDemo.InsultsReport.rdlc""Model"
                        new InsultsDataTable().AddTestDataRows());



Scenario 3: Rdlc is in a separate assembly from the datasource

So far we have been assuming that the datasource is in the same assembly as the embedded rdlc file. This is not always the case. If they are in different assemblies, we need to specify which assembly to look in. There are a couple of ways to do get the assembly, but I prefer using a class I know to be in the same assembly.

typeof(KnowClassFromAssembly).Assembly

again, here's the method signature:



Approvals
.ApproveReport( reportName, rdlcAssembly,
                         reportDatasourceName reportData);




Scenario 4: Multiple Data Sources
The last case is when there are multiple datasource, in this case you need to make tuples for each pairing.


Approvals.ApproveReport( reportName, rdlcAssembly, reportPairing[]);


Here's the code:

Approvals.ApproveReport("ReportingDemo.InsultsReport.rdlc",
     typeof (KnowClassFromAssembly).Assembly,
     Tuple.Create("Model", new InsultsDataTable().AddTestDataRows()),
     Tuple.Create("Address", CompanyInformation.Instance));


So now you have everything you need to easy get those rdlc reports under tests.

Have fun testing!
Llewellyn Falco

Note: there is a slight variation between the Page size of a PDF and a multipage Tiff.
If your report is very tight to the page, the page rendering might be different.

3 comments:

Nidhi said...

Thanks for nice blog.
I have one question. How can we Approve rdlc Report Which has Parameter included in it?I have rdlc report which has 3 paramaters , I want to Approve it with ApprovalTests.

Thanks

Llewellyn Falco said...

Nidhi, sorry for the late reply, I didn't check the comments...

As for parametered reports I don't know (not really an expert on Rdlc reports, more of a noob).

However, Grab the code from the RdlcApproval class and look at it. I'm sure it's possible to pass in a parameter.

Unknown said...

I am trying to approve rdlc file with Nunit 2.6 test framework. It seems it doesn't support Nunit 2.4 onwards versions.
I got error "ApprovalTests doesn't recognize your test framework" for this piece of code:

[TestCase(Severities.Severity4, TestName = ReportConstants.ReportName + "Severity4")]
[UseReporter(typeof(DiffReporter))]
public void InEventBasedReportContentsAreMatchingFor()
{
RdlcApprovals.VerifyReport("AcceptanceTests.NewReport.rdlc", typeof(WhenUserClickOnReportButton_Verify).Assembly, Tuple.Create("SingleValuedFields", reportDataSet.Tables["SingleValuedFields"]));
}

It worked when I replaced [TestCase] attribute with [Test] attribute on my test method. I think ApprovalTests support for Nunit 2.6 framework will simplify executing Nunit data driven tests.