Chapter 9. Working with collections

Testing lists of objects is usually a pain, but FitNesse makes it very easy. So far, we have always been testing a predefined number of items or steps. Now we'll learn how to verify a bunch of objects at once using ArrayFixture and RowFixture.

In this chapter, we'll implement the following user story:

View tickets

As a player, I want to view my tickets, so that I can find out if I have won and how much.

The customers tell us that a player should be able to view all his open tickets (tickets for open draws) and all his tickets for any particular draw. For open draws, we need to show selected numbers, ticket value and draw date. When the player views tickets by draw, we should display selected numbers, ticket value and winnings.

Our job is done when the following test cases run correctly:

  • A draw on 01/01/2008 is open, and player John has 100 dollars in his account. He buys a single ticket for numbers 1, 3, 4, 5, 8, 10; another single ticket for 2, 4, 5, 8, 10, 12; and five tickets for numbers 3, 6, 9, 12, 15, 18. When he views his open tickets, he should see three tickets for the draw on 01/01/2008, with 10 dollars on the first two sets of numbers and 50 dollars on the third set of numbers.

  • Two players have tickets in the same draw. Tom buys a ticket with numbers 2, 4, 5, 8, 10, 12 for the draw on 01/01/2008. John buys a ticket with numbers 1, 3, 4, 5, 8, 10 for the same draw. When Tom views tickets for the draw on 01/01, he should see only his ticket, not John’s. Likewise for John.

  • Draws on 01/01, 02/01 and 03/01 are open. Player John has 100 dollars in his account. He buys a ticket with the numbers 1, 3, 4, 5, 8,10 for the draws on 01/01 and 02/01, and five tickets with numbers 3, 6, 9, 12, 15, 18 for the draw on 01/01. When he views his tickets for the draw on 01/01, he should see 10 dollars on 1, 3, 4, 5 ,8, 10 and 50 dollars on 3, 6, 9, 12, 15, 18. For the draw on 02/01, he should see just 10 dollars on 1, 3, 4, 5, 8, 10. All tickets are open. For the draw on 03/01, he should see no tickets.

  • Continuing the third test, numbers 1, 3, 4, 5, 31, 32 are drawn on 01/01/2008, and John has a winning ticket with four correct numbers. Now, when he lists tickets for the draw on 01/01, he should see that both tickets are closed, and that the 10-dollar ticket has three dollars of associated winnings (the total pool was 60 dollars, the payout pool was 30 dollars, and the 4-out-of-6 prize is 10% of this). When John lists his open tickets, he should only see the ticket for 02/01.

These test scripts give us some new ideas about the problem domain. As we learned in the previous chapter, tickets can have different values. But in this case, our clients want five tickets for 10 dollars on the same set of numbers and for the same draw to appear as one 50-dollar ticket. The test scripts also call for a new kind of test: checking the contents of a list of objects (tickets a player has in a draw).

Testing lists of objects

All four tests for this chapter use a draw on 01/01 and a player called John. So let's start the test, open the draw and create this player in a common setup page:

TicketReviewTests.SetUp


1   !|Tristan.Test.ReviewTickets|
2   
3   |Draw on |01/01/2008| is open|
4   
5   |Player | john | opens account with | 100 | dollars|

The first test should look like this:

TicketReviewTests.SeveralTicketsOneDraw


1   |Player|john|buys a ticket with numbers|1,3,4,5,8,10|for draw on|01/01/2008|
2   
3   |Player|john|buys a ticket with numbers|2,4,5,8,10,12|for draw on|01/01/2008|
4   
5   |Player|john|buys|5|tickets with numbers|3,6,9,12,15,18|for draw on|01/01/2008|
6   
7   |Player|john|lists open tickets|
8   |draw|numbers|value|
9   |01/01/2008|1,3,4,5,8,10|10|
10  |01/01/2008|2,4,5,8,10,12|10|
11  |01/01/2008|3,6,9,12,15,18|50|

To implement this test, we need to find open tickets of a particular player. Let's add a method for this to DrawManager:

Tristan/src/IDrawManager.cs


22      List GetOpenTickets(int playerId);

Everything except the last table in the test looks very similar to methods and tables from the previous two chapters. The last table presents us with a new problem, because we need to check a list of elements. In Embed fixtures for best results we used a ColumnFixture to check several accounts at once, but the situation here is a bit different. This test can return more or less rows than expected. Implementing this test with ColumnFixture would be relatively tricky. We would need separate checks for collection size and collection contents. In the collection contents test, we would have to create a wrapper to fetch an element by some key property (assuming that there is a key property), and then test other properties. DoFixture has a very useful shortcut for tests like these: it just returns the whole list. See the method PlayerListsOpenTickets below.

Tristan/test/ReviewTickets.cs


9     public class ReviewTickets:fitlibrary.DoFixture
10    {    
11      private IDrawManager _drawManager;
12      private IPlayerManager _playerManager;
13      public ReviewTickets()
14      {
15        _playerManager = new PlayerManager();
16        _drawManager = new DrawManager(_playerManager);
17      }
18      public void DrawOnIsOpen(DateTime drawDate)
19      {
20        _drawManager.CreateDraw(drawDate);
21      }
22      public void PlayerOpensAccountWithDollars(String player, decimal balance)
23      {
24        PlayerRegistrationInfo p = new PlayerRegistrationInfo();
25        p.Username = player; p.Name = player;
26        p.Password = "XXXXXX";
27        // define other mandatory properties
28        int playerId = _playerManager.RegisterPlayer(p);
29        _playerManager.AdjustBalance(playerId, balance);
30      }
31      public void PlayerBuysATicketWithNumbersForDrawOn(
32        string username, int[] numbers, DateTime date)
33      {
34        PlayerBuysTicketsWithNumbersForDrawOn(username, 1, numbers, date);
35      }
36  
37      public void PlayerBuysTicketsWithNumbersForDrawOn(
38        string username, int tickets, int[] numbers, DateTime date)
39      {
40        int pid = _playerManager.GetPlayer(username).PlayerId;
41        _drawManager.PurchaseTicket(date, pid, numbers, 10 * tickets);
42      }
43      public IList PlayerListsOpenTickets(String player)
44      {
45        return _drawManager.GetOpenTickets(
46          _playerManager.GetPlayer(player).PlayerId);
47      }
58    }

If a DoFixture method returns an array or an IEnumerable collection, the result is automatically wrapped into an ArrayFixture for testing. ArrayFixture is a FitLibrary class for testing arrays and collections. It can check contents of an array and verify that there are no additional or missing elements. The second row of an ArrayFixture table lists element properties,[22] and all the following rows contain the expected contents of the array. Properties not included in the table are just ignored when comparing actual results with expected results, so you can hide unimportant details. ArrayFixture compares the array or collection with the table by checking all the given properties, in the order of the elements listed in the table. It reports any elements out of order, elements that were not in the test method result (marked as missing), and elements returned by the test method that were not expected in the table (marked as surplus).

The first test is now complete (Figure 9.1, “DoFixture automatically wraps arrays and IEnumerable collections into ArrayFixture, allowing us to easily verify their contents. ”). Now that we know how to test lists of objects, we can easily write the second and third test cases for this chapter. Let’s check for the tickets using the table header “Player XXX lists tickets for draw on YYY”. The second test looks like this:

TicketReviewTests.TwoAccountsOneDraw


1   |Player|tom|opens account with|50|dollars|
2   
3   |Player|john|buys a ticket with numbers|1,3,4,5,8,10|for draw on|01/01/2008|
4   
5   |Player|tom|buys a ticket with numbers|2,4,5,8,10,12|for draw on|01/01/2008|
6   
7   |Player|john|lists tickets for draw on|01/01/2008|
8   |value|numbers|
9   |10|1,3,4,5,8,10|
10  
11  |Player|tom|lists tickets for draw on|01/01/2008|
12  |value|numbers|
13  |10|2,4,5,8,10,12|

To implement it, we’ll need a method in the main test fixture that returns a list of tickets in a draw for a player:

Tristan/test/ReviewTickets.cs


48      public IList PlayerListsTicketsForDrawOn(
49        String player, DateTime date)
50      {
51        return _drawManager.GetTickets(
52          date,_playerManager.GetPlayer(player).PlayerId);
53      }

And a corresponding method in DrawManager:

Tristan/src/IDrawManager.cs


23      List GetTickets(DateTime drawDate, int playerId);

[Tip]How do I test arrays of strings or ints?

Although embedded types don’t have any properties you can put into the table header, they all have a ToString method, which will do just fine for this purpose. So your table might look like this:

!|Some method returning array of ints | 
| ToString | 
| 1 |
| 2 | 
| 5 |

Remember the exclamation mark at the beginning of the table to prevent CamelCase formatting of ToString.

Figure 9.1. DoFixture automatically wraps arrays and IEnumerable collections into ArrayFixture, allowing us to easily verify their contents.

DoFixture automatically wraps arrays and IEnumerable collections into ArrayFixture, allowing us to easily verify their contents.

[Note]Stuff to remember
  • You can use ArrayFixture from FitLibrary and RowFixture from the basic FIT package to test lists of elements.

  • If a DoFixture method returns an array or an IEnumerable collection, the result is automatically wrapped into an ArrayFixture for testing.

  • RowFixture can use symbols, keywords and partial row-key mapping, so it is better then ArrayFixture when you need more precision.

  • ArrayFixture checks for missing and additional elements.

  • You must specify element structure (second row) even when checking for an empty collection.

  • Columns with a question mark in RowFixture are excluded from the “primary key”.



[22] Properties in a general sense: fields and methods (without parameters) can also be used.