3

I'm asked to write some unit tests for some functionality, but quite frankly I'm not so sure about the need or usefulness of doing so for this particular piece of code. I'm in no way trying to question the need or usefulness of unit testing in general.

The code in question is very trivial and gets used alot. Basically it's a wrapper around the .Skip() and .Take() extension methods. In my opinion the legitimacy of the methods as a whole is questionable.

The code is basically this:

public IQueryable<T> Page(IQueryable<T> query, int page, int size)
{
    if(query == null) throw new ArgumentNullException("query");
    if(page < 0) throw new ArgumentOutOfRangeException("page"); 
    if(page < 0) throw new ArgumentOutOfRangeException("size"); 

    return query.Skip(page * size).Take(size);
}

Of course I can unit test for the expected exceptions, but what else? Could very well be that I'm missing the point, so what's up with this?

vcsjones
  • 138,677
  • 31
  • 291
  • 286
fuaaark
  • 541
  • 8
  • 21
  • You could write a test to ensure it's diferring execution by passing an `IQueryable` that would throw if iterated and not iterating the result. – Servy Dec 03 '12 at 15:26
  • 1
    1. The third `if` statement has a problem. 2. Look at the fabulous EduLINQ project from Jon Skeet and what tests he wrote during the course of reimplementing the LINQ extension methods. – Stefan Hanke Dec 03 '12 at 15:29
  • @fuaaark Will you clarify if you've been asked to test this method connected to the underlying database or really unit-testing it? – Matías Fidemraizer Dec 03 '12 at 16:18
  • Isn't the third if statement incorrect? It should be `if(size < 0) throw new ArgumentOutOfRangeException("size");` – Shoe Dec 03 '12 at 16:31
  • You're right, this isn't the actual code by the way. It doesn't differ by much – fuaaark Dec 04 '12 at 06:50

4 Answers4

3

You can just hand in a static collection by calling AsQueryable

List<T> dummyData = new List<T>();
//Add data
var result = Page(dummyData.AsQueryable(), 0, 10);
//Perform assertions on result.

If you are in-fact just trying to test that your pagination works correctly.

vcsjones
  • 138,677
  • 31
  • 291
  • 286
  • Good idea, in my answer I thought about creating test data. In your case, it's an actual unit test, rather than mine, which is an integration one. – Matías Fidemraizer Dec 03 '12 at 15:30
2

You can test quite a few things here:

  1. Check for proper guards (the exceptions that are thrown when invalid parameters are passed).
  2. Check that Skip was called with the correct parameter.
  3. Check that Take was called with the correct parameter.
  4. Check that Skip was called before Take.

Points 2 - 4 are best tested with a mock. A mocking framework comes in handy here.
This kind of testing is called "interaction-based testing".

You could also use "state-based testing" by calling the method under test with a list with data and check that the returned data is the correct subset.

A test for point 2 could look like this:

public void PageSkipsThePagesBeforeTheRequestedPage()
{
    var sut = new YourClass();
    var queryable = Substitute.For<IQueryable<int>>();

    sut.Page(queryable, 10, 50);

    queryable.Received().Skip(500);
}

This test uses NSubstitute as mocking framework.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • Okay, sounds good. What I'm not clear about is when using this piece of tested functionality in other code, how do I verify a call has been made to it in a clean way? I could set some public property when the method gets called, but that just feels very ugly... – fuaaark Dec 04 '12 at 07:48
  • @fuaaark: No, don't set a public property. What exactly do you want to test? Do you want to test that another method called the `Page` method? – Daniel Hilgarth Dec 04 '12 at 10:22
  • Yeah, since the Page() method is used in alot of different places, I only want to unit test it once. But I need to verify it's being called. – fuaaark Dec 04 '12 at 10:25
  • @fuaaark: And you want to verify it for all those different places it is being used in? – Daniel Hilgarth Dec 04 '12 at 10:46
  • Yeah, I guess so, how else should I verify unit tested code used by other code? – fuaaark Dec 04 '12 at 10:59
  • @fuaaark: Is `Page` the implementation of a method defined on an interface? – Daniel Hilgarth Dec 04 '12 at 13:10
  • Yeah, well sort of, I've extracted the interface. – fuaaark Dec 04 '12 at 13:32
  • @fuaaark: Than you can use the same approach. Give the class you want to test a mock of that interface and verify that `Page` was called with the correct parameters. – Daniel Hilgarth Dec 04 '12 at 13:33
0

In fact, since IQueryable<T> may encapsulate an underlying data storage - which is the case of Entity Framework's queryables -, we're talking about integration tests.

In my case, if I'd need to test something like your piece of code, I'd store some test data in the database for later query for it.

Since I can expect the results, I can perform an assertion in order to check if the tested method returned the expected result.

UPDATE

Since I see some confusion with the downvote and some comments, I'd like to point out that I've answered this taking in account the fact that the answer is tagged as for Entity Framework and I guess that the queryable was obtained from the DbSet.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • No need for an integration test here. You should simply use an `IQueryable` implementation that works in memory. – Daniel Hilgarth Dec 03 '12 at 15:35
  • + 1 to Daniel. I was about to write similar comment. – Mikhail Dec 03 '12 at 15:37
  • @DanielHilgarth If you downvoted my answer, you should double-check it. I'm not saying "do an integration test". I said that **OP's code is an integration test rather than an unit test**................... – Matías Fidemraizer Dec 03 '12 at 15:42
  • 1
    @MatíasFidemraizer: I didn't downvote. But your comment is not correct. 1) The OP didn't show any testing code, so you can't know what type of test it is. The code he showed is that of a simple method. Certainly not that of an integration test. 2) The method he showed doesn't require an integration test. – Daniel Hilgarth Dec 03 '12 at 15:46
  • @DanielHilgarth Check the `[EntityFramework]` tag in the OP's question. Maybe I'm crazy, but when I read OP's code and I check that's tagged as for Entity Framework, I suspect that the queryable comes from the `DbSet`! – Matías Fidemraizer Dec 03 '12 at 15:54
  • @MatíasFidemraizer: The tag is wrong. `IQueryable` is an interface, nothing more. It is not specific to EF. Do you see any EF related code in the method he wants to test? No? See, no integration test. – Daniel Hilgarth Dec 03 '12 at 15:55
  • @DanielHilgarth You're very closed mind then. The code does nothing about EF, but it's just **a sample code**. Do you know how SO works? If you know that, you'll know that if someone tags a question as for Entity Framework, **the code has something to do with Entity Framework**. See: this is an integration test! – Matías Fidemraizer Dec 03 '12 at 15:58
  • @MatíasFidemraizer: Sorry, but that's just nonsense. Insulting me doesn't change that fact. As you are unwilling to see that, I am out of this discussion as it is a waste of time. The point is: This code *can* be tested with a regular unit test. An integration test is *not* needed. That doesn't mean that you can't write an integration test additionally. – Daniel Hilgarth Dec 03 '12 at 16:00
  • @DanielHilgarth Ok, my point about you didn't read the first sentence of my answer: **may encapsulate an underlying data storage - which is the case of Entity Framework's queryables -, we're talking about integration tests.**. Sometimes I believe that the point is taking an assumption as the truth. I'm trying to help the OP and since I suspect he's obtaining the queryable from the `DbSet`, let me know what's wrong. – Matías Fidemraizer Dec 03 '12 at 16:09
  • @DanielHilgarth Imagine that I'd say "[...]it's encaluspalting[...]" instead of "[...]may encapsulate[...]". It would be different, wouldn't it? I'm not saying "it's that", I'm saying "it seems that...". – Matías Fidemraizer Dec 03 '12 at 16:10
  • @MatíasFidemraizer: The point of a unit test is to test something in isolation. This in mind, it doesn't matter where the actual program get's its implementation of `IQueryable`. – Daniel Hilgarth Dec 03 '12 at 16:12
  • @MatíasFidemraizer: I updated my answer, illustrating what I am saying here. As you can see, the test is in isolation, there is no DbSet anywhere. It is simply not necessary for testing this simple method. – Daniel Hilgarth Dec 03 '12 at 16:15
  • @DanielHilgarth Again, open your mind. Can't you understand that there're a lot of people calling their tests as *unit tests* because Visual Studio does not clearly distinguish both kind of tests? Thank you for the clarification, but I really know what's an actual unit test. That's why my answer says: **[...]we're talking about integration tests**. It's that easy: if you want to implement an unit test, you wouldn't use an EF queryable, but because the question is about EF, what makes you think that the queryable came from a `List` object in the OP's sample code? – Matías Fidemraizer Dec 03 '12 at 16:16
  • @MatíasFidemraizer: I am sorry to say that but your comment is just not correct. I will repeat my self one last time: Your answer is wrong, we are not talking necessarily about an integration test here. `IQueryable` has nothing to do with EF. Period. When you test this method you simply should not care about the origin of the `IQueryable`. I don't make the assumption it is coming from a list, so you last question is strange. I am out. – Daniel Hilgarth Dec 03 '12 at 16:19
  • @DanielHilgarth Repeat yourself whatever you want, but don't tell *we are not talking about...*. This is you! Check **vcsjones** answer and his last sentence: *if you are in-fact just trying to test that your pagination works correctly*. Again: maybe I'm crazy, but he's saying the same thing like me. The meaning of his last sentence is **"if you're trying to test that pagination works correctly against a fake queryable**! – Matías Fidemraizer Dec 03 '12 at 16:22
0

I'm sure, there is a strong need to test this method. First of all, because of the bug:

if(page < 0) throw new ArgumentOutOfRangeException("page"); 
if(page < 0) throw new ArgumentOutOfRangeException("size"); 

You're not checking the size variable in fact.

Anyway, there is no need to perform the "integrated" testing - you can simply mock your IQueryable object and perform the unit testing. (see this answer: How do I mock IQueryable<T>) In my opinion, it is an absolutely right thing to do to test this method.

Community
  • 1
  • 1
Mikhail
  • 898
  • 7
  • 21