Hi, I'm trying to unit test a common interface used in a generic Repository, something like T GetWhere(Func<T, Boolean> where)...
When I set this expectation in a test
Mock.Arrange(() => bookRepo.GetWhere(book => book.Id == 1))
.Returns(expectedBook)
.MustBeCalled();
JustMock throws this exception: "Telerik.JustMock.MockAssertionException: Setup contains calls that are marked as "MustBeCalled" but actually never called"
It seems the GetWhere() method was never call, to return the expected object.
Below is a simple example that illustrate the case.
How should I rewrite the second test below so that I can mock and test these type of interfaces?
Thanks in advance.
using
System;
using
Microsoft.VisualStudio.TestTools.UnitTesting;
using
Telerik.JustMock;
//Free version 2011.1.331.1
using
Model.Infrastructure;
using
Model;
namespace
Model.Infrastructure
{
public
interface
IRepository<T> where T :
class
{
T GetById(
int
Id);
T GetWhere(Func<T, Boolean> where);
}
}
namespace
Model
{
public
class
Book
{
public
int
Id {
get
;
set
; }
public
string
BookTitle {
get
;
set
; }
public
string
OnLoanTo {
get
;
set
; }
public
Book(){}
}
public
class
BookService
{
private
IRepository<Book> _bookRepository;
public
BookService(IRepository<Book> bookRepository)
{
this
._bookRepository = bookRepository;
}
public
Book GetSingleBook1(
int
bookId)
{
return
_bookRepository.GetById(bookId);;
}
public
Book GetSingleBook2(
int
bookId)
{
Book b = _bookRepository.GetWhere(x => x.Id == bookId);
return
b;
}
}
}
namespace
Model.Tests
{
[TestClass]
public
class
AssortedTests
{
private
Book expectedBook;
private
IRepository<Book> bookRepo;
private
BookService bookService;
[TestInitialize]
public
void
Setup()
{
expectedBook =
new
Book()
{ Id = 1,
BookTitle =
"Adventures"
,
OnLoanTo =
"John Smith"
};
bookRepo = Mock.Create<IRepository<Book>>();
bookService =
new
BookService(bookRepo);
}
[TestMethod]
public
void
Book_CanRetrieveBookFromRepository()
{
Mock.Arrange(() => bookRepo.GetById(1))
.Returns(expectedBook)
.MustBeCalled();
//Act
Book actualBook = bookService.GetSingleBook1(1);
Mock.Assert(bookRepo); //Pass...
Assert.IsTrue(actualBook.BookTitle ==
"Adventures"
);
}
[TestMethod]
public
void
Book_CanRetrieverFromRepositoryUsingLambdaExpressions()
{
Mock.Arrange(() => bookRepo.GetWhere(book => book.Id == 1))
.Returns(expectedBook)
.MustBeCalled();
//Act
Book actualBook = bookService.GetSingleBook2(1);
Mock.Assert(bookRepo);
//Throws the error...
Assert.IsTrue(actualBook.BookTitle ==
"Adventures"
);
}
}
}
11 Answers, 1 is accepted
Thanks again for sending the issue. However, you could write the failing test in the following way that will make it work:
[TestMethod]
public void Book_CanRetrieverFromRepositoryUsingLambdaExpressions() //Fail...
{
Mock.Arrange(() => bookRepo.GetWhere(Arg.IsAny<
Func
<Book, Boolean>>()))
.Returns(expectedBook)
.MustBeCalled();
//Act
Book actualBook = bookService.GetSingleBook2(1);
Mock.Assert(bookRepo);
Assert.IsTrue(actualBook.BookTitle == "Adventures");
}
But your report is legitimate and seems to be the case for other popular tools as well. Like the one I found in this stackoverflow thread:
http://stackoverflow.com/questions/5196669/moqing-methods-where-expressionfunct-bool-are-passed-in-as-parameters
But we would love to implement this as a feature and thanks again for pointing this to us. You can further follow the task in the following PITS entry:
http://www.telerik.com/support/pits.aspx#/public/justmock/5515
Kind Regards,
Mehfuz
the Telerik team
I found the test also pass if "IgnoreArgument()" is used instead of your proposed workaround:
Arg.IsAny<
Func
<Book, Boolean>>()
. In either case, I feel the test is not quite-right as it is ignoring the argument anyway. [TestMethod]
public
void
Book_CanRetrieverFromRepositoryUsingLambdaExpressions()
{
Mock.Arrange(() => bookRepo.GetWhere(b => b.Id == 1))
.IgnoreArguments()
//Now it pass, but accepts any argument!
.Returns(expectedBook)
.MustBeCalled();
//Act
Book actualBook = bookService.GetSingleBook2(Arg.IsAny<
int
>());
Mock.Assert(bookRepo);
Assert.IsTrue(actualBook.BookTitle ==
"Adventures"
);
}
I'm glad this Mocking methods where Expression<Func<T, bool>> are passed in as parameters is going to be implemented as a feature for the product. Great!
Thanks again for your reply. Yes you are right that you can use IgnoreArguments as well instead of
Arg.IsAny<
Func
<Book, Boolean>>()
. Sorry that i forgot to mention it. Also, its great that you pointed this Mocking methods where Expression<Func<T, bool>> are passed in as parameters out.Should you have any more issues , please don't hesitate to write us back.
Kind regards,
Mehfuz
the Telerik team
Thanks!
Thanks for bringing up the issue.
The original bug that was mentioned in PITS is fixed now:
http://www.telerik.com/support/pits.aspx#/public/justmock/5515
Since the argument is a query expression itself, It will be redundant to wrap it around a Arg.Matcher as you can always do this:
Mock.Arrange(() => repository.GetWhere(book => book.Id == 1)).Returns(expected).MustBeCalled();
Hope this answers your question.
Kind Regards
Mehfuz
the Telerik team
Here is my test:
<code>
var charge = new Charge();
var charges = new[] {charge};
Mock.Arrange(() => repo.Get<Charge>(c => c.OrgID == 1 && c.IsActive, Arg.IsAny<List<string>>())).Returns(charges).Occurs(1);
</code>
And here is my code under test:
<code>
var list = genericRepository.Get<Charge>(c => c.OrgID == orgID && c.IsActive, new List<string> { "ChargeCode" });
</code>
My test fails when I assert my repo mock object saying it was called 0 times. Even if I change my code under test to use an explicit 1, the assertion fails. I upgraded to the latest Nuget package for JustMock (lite). Do you have any suggestions?
Thank you, again.
Thanks a lot for the snippet.
However, I would ask you to send us a sample project which will let us debug the issue further.
Here are few things that you might like to watch out:
- Since you are using lite version make sure that you are not mocking a non-virtual method.
- Wrap the expression argument in a variable and then pass it both for arrange/actual execution and please check that it works.
Mehfuz
the Telerik team
Sorry for the delayed response. I've whipped up a quick sample of my problem, but not sure how to get the ZIP to you.
In order to send us the example, it will be best for you to open a new support ticket and to attach the .ZIP in your post.
Feel free to ask for further assistance.
Regards,
Kaloyan
the Telerik team
Here is my class:
public class Process
{
private IRepo repos;
public Process(IRepo repo)
{
repos = repo;
}
public List<
Payment
> GetPayments(bool paid, int acctNo)
{
Expression<
Func
<Payment, bool>> func = p => p.IsPaid == paid && p.ToAccountId == acctNo;
var result = repos.Get<
Payment
>(func);
return result.ToList();
}
}
And here is my test:
public class ProcessTest
{
private IRepo mockRepo;
[TestInitialize]
public void Setup()
{
mockRepo = Mock.Create<
IRepo
>();
var payment = new Payment() {IsPaid = false, ToAccountId = 1234};
var list = new[] {payment};
Expression<
Func
<Payment, bool>> func = p => p.IsPaid == false && p.ToAccountId == 1234;
Mock.Arrange(() => mockRepo.Get<
Payment
>(func, null)).Returns(list).OccursOnce();
}
[TestMethod]
public void TestMethod1()
{
var sut = new Process(mockRepo);
var result = sut.GetPayments(false, 1234);
Mock.Assert(mockRepo);
}
}
This test fails and I don't know why.
Does the Expression have to be the exact same object?
What if I want to use Arg.Matches() and just match on a part of the Expression?
Thanks again for reaching out to us.
Your test looks fine, however it’s an issue with AndAlso type expression processing in JustMock. Good news is that we fixed the issue and should you need a fix urgently then please open a support where I will send you the build.
On your second question, you don't need Arg.Matches since it’s a query parameter itself and using of Arg.Matches is redundant as your main goal here is to match a mocked method based on the expression you supplied in Mock.Arrange which you can write directly without the help of Arg.Matches
Mehfuz
the Telerik team