How to Unit Test a Method in Sitecore MVC having tightly coupled Dependency on two distinct Sitecore Contexts? - unit-testing

I have to unfortunately write Unit Tests for a legacy Sitecore MVC code base where two distinct Sitecore Contexts are called. I understand this comes under Integration Testing but i don't have the option of educating my project Leads on that front. So i have chosen to use FakeDb for emulating Sitecore Instance and NSubstitute for substituting injected Dependencies (can't use any Profilier API Frameworks like MS Fakes, TypeMock etc because of Budget constraints). I am providing the code below:
Method to be UnitTested
public bool DubiousMethod()
{
// This HttpContext call is pain area 1. This gets resolved when i call it using ItemContextSwitcher in Unit Tests.
string currentUrl = HttpContext.Current.Request.RawUrl;
// This Sitecore Context call to Site Name is pain area 2. This gets resolved when Unit Tests are run under SiteContextSwitcher.
string siteName = Sitecore.Context.Site.Name;
return true/False;
}
Unit Test Method
[Fact]
public void DubiousMethodUT()
{
// create a fake site context
var fakeSite = new Sitecore.FakeDb.Sites.FakeSiteContext(
new Sitecore.Collections.StringDictionary
{
{ "name", "website" }, { "database", "web" }, { "rootPath", "/sitecore/content/home" },
{ "contentStartItem", "home"}, {"hostName","https://www.myorignalsiteurl.com"}
});
using (new Sitecore.Sites.SiteContextSwitcher(fakeSite))
{
//DubiousClassObject.DubiousMethod(home) // When Debugging after uncommenting this line i get correct value in **Sitecore.Context.Site.Name**
using (Sitecore.FakeDb.Db db = new Sitecore.FakeDb.Db
{
new Sitecore.FakeDb.DbItem("home") { { "Title", "Welcome!" } ,
new Sitecore.FakeDb.DbItem("blogs") }
})
{
Sitecore.Data.Items.Item home = db.GetItem("/sitecore/content/home");
//bool abc = confBlogUT.IsBlogItem(home);
using (new ContextItemSwitcher(home))
{
string siteName = Sitecore.Context.Site.Name;
var urlOptions = new Sitecore.Links.UrlOptions();
urlOptions.AlwaysIncludeServerUrl = true;
var pageUrl = Sitecore.Links.LinkManager.GetItemUrl(Sitecore.Context.Item, urlOptions);
HttpContext.Current = new HttpContext(new HttpRequest("", pageUrl.Substring(3), ""), new HttpResponse(new StringWriter()));
Assert.False(DubiousClassObject.DubiousMethod(home); //When Debugging after commenting above DubiousMethodCall i get correct value for **HttpContext.Current.Request.RawUrl**
}
}
}
}
As you can observe that when i try to call the method from FakSiteContext then i am getting the correct value for Sitecore.Context.Site.Name however my code breaks when HttpContext.Current.Request.RawUrl is invoked in the method. Opposite happens when i invoke the method from ContextItemSwitcher(FakeItem) context. So far i have not been able to find a way to merge both the Contexts (which i believe is impossible in Sitecore). Can anyone suggest if i run my Unit Tests in an overarching context where i am able to contrl fakeSite Variables as well as FakeItem context variables as well and by extensions any other Sitecore Context calls?
Any help would be appreciated.

I'd recommend to take a look at Unit testing in Sitecore article as it seem to be what you need.
In short - you'll need to do a few adjustments in your code to make it testable:
1) Replace static HttpContext with abstract HttpContextBase (impl. HttpContextWrapper) so that everything can be arranged - DubiousMethod gets an overload that accepts DubiousMethod(HttpContextBase httpContext).
2) As for Sitecore Context data - it has Sitecore.Caching.ItemsContext-bound semantics (as mentioned in the article), so you could cleanup the collection before/after each test to get a sort of isolation between tests.
Alternatively you could bake a similar wrapper for Sitecore.Context as ASP.NET team had done for HttpContext -> HttpContextBase & impl HttpContextWrapper.

Related

Navigation Unit Testing in MvvmCross

Trying to unit test the navigation in one of my command calls into a private method. Just trying to test if the navigation request has been made as a result of this command execution.
There's the old documentation;
https://www.mvvmcross.com/documentation/fundamentals/testing
This documentation does not factor in new async based calls as far as I found; For example IMvxMainThreadAsyncDispatcher
Either we need to implement two ExecuteOnMainThreadAsync methods or inherit from MvxMainThreadAsyncDispatcher in MockDispatcher.
Also need to add IMvxMainThreadAsyncDispatcher in IoC registration.
var mockDispatcher = new MockDispatcher();
...
...
Ioc.RegisterSingleton<IMvxMainThreadAsyncDispatcher>(MockDispatcher);
So almost all tests work except navigation call requests. Below method inside MockDispatcher never gets called so I can't check request counts;
public async Task<bool> ShowViewModel(MvxViewModelRequest request)
{
Requests.Add(request);
return true;
}
Anybody has a working code that would mock and gets this Request called or in some other form? IMvxMainThreadDispatcher is being set as absolute, and navigation calls are not done with ShowViewModel<>() anymore in MVVMCross, it's done by calling navigationService.Navigate();
Well, I have found the solution to my question... The ShowViewModel is called when navigation service is properly mocked. I have found a piece of code on GitHub from MvvmCross's own repo on how they do tests for navigation. My next challenge would be to mock the destination viewModel but that's separate and below code doesn't cover that. Previously I had a very basic IMvxNavigationService mock.
var mockLocator = new Mock<IMvxViewModelLocator>();
mockLocator.Setup(
m => m.Load(It.IsAny<Type>(), It.IsAny<IMvxBundle>(), It.IsAny<IMvxBundle>(), It.IsAny<IMvxNavigateEventArgs>())).Returns(() => new FakeViewModel());
mockLocator.Setup(
m => m.Reload(It.IsAny<IMvxViewModel>(), It.IsAny<IMvxBundle>(), It.IsAny<IMvxBundle>(), It.IsAny<IMvxNavigateEventArgs>())).Returns(() => new FakeViewModel());
var mockCollection = new Mock<IMvxViewModelLocatorCollection>();
mockCollection.Setup(m => m.FindViewModelLocator(It.IsAny<MvxViewModelRequest>()))
.Returns(() => mockLocator.Object);
Ioc.RegisterSingleton(mockLocator.Object);
var loader = new MvxViewModelLoader(mockCollection.Object);
_navigationService = new MvxNavigationService(null, loader)
{
ViewDispatcher = MockDispatcher,
};
_navigationService.LoadRoutes(new[] { typeof(YourViewModelTestClass).Assembly });
Ioc.RegisterSingleton<IMvxNavigationService>(_navigationService);

Moq out parameters

I'm fairly new to using Moq and Nunit for unit testing and I'm having issues with one scenario. What I want is for my mock to have an out parameters which my system under test will then use to decide what action to take.
My system under test is an MVC API controller and in particular I'm trying to test the POST method. I want to return an error message for the object when validation fails.
Here is the method code for the controller:
public IHttpActionResult Post(Candidate candidate)
{
try
{
if(candidate==null)
return BadRequest();
IEnumerable<string> errors;
_candidateManager.InsertCandidate(candidate, out errors);
if (errors!=null && errors.Any())
return BadRequest(CreateErrorMessage("Invalid candidate: ", errors));
return CreatedAtRoute("DefaultApi", new {id = candidate.CandidateId}, candidate);
}
catch (Exception)
{
return InternalServerError();
}
}
This is my Unit Test Code:
[Test]
[Category("CandidateManagerController Unit Tests")]
public void Should_Return_Bad_Request_When_Creating_Invalid_Candidate()
{
IEnumerable<string> errors = new List<string>() {"error1", "error2"};
var mockManager = new Mock<ICandidateManager>();
mockManager.Setup(x => x.InsertCandidate(new Candidate(), out errors)).Callback(()=>GetErrors(errors));
var sut = new CandidateManagerController(mockManager.Object);
var actionResult = sut.Post(new Candidate());
Assert.IsInstanceOf<BadRequestResult>(actionResult);
}
What I expect is that when _candidateManager.InsertCandidate() is run then the errors variable is populated. However what is happening is that when you step through the controller code errors is null after _candidateManager.InsertCandidate() method is run.
If anyone has any ideas what I'm doing wrong or if what I want to do is not possible using Moq then please let me know.
Thanks
What you want to do is possible. If you look at the Quickstart docs at https://github.com/Moq/moq4/wiki/Quickstart, there is a section where it shows how you do setups for methods using out params. I have made two corrections to your code and it works.
You have to use the same candidate instance for both the mock setup and when you exercise the sut. Otherwise, Moq thinks that the two objects are different and your test setup becomes useless.
You don't have to use Callback in order to set the errors returned by the mocked CandidateManager.
Below is your test method with my changes.
[Test]
[Category("CandidateManagerController Unit Tests")]
public void Should_Return_Bad_Request_When_Creating_Invalid_Candidate()
{
IEnumerable<string> errors = new List<string>() {"error1", "error2"};
//instance to be used for both setup and test later
var candidate = new Candidate();
var mockManager = new Mock<ICandidateManager>();
//removed Callback
mockManager.Setup(x => x.InsertCandidate(candidate, out errors));
var sut = new CandidateManagerController(mockManager.Object);
var actionResult = sut.Post(candidate);
Assert.IsInstanceOf<BadRequestResult>(actionResult);
}
You have to make sure that when you call your SUT that you use the same instance passed to the out argument otherwise the call will fail.
In your example, the method under test passes a null instance into the mocked method thus negating the setup of the test.
If however you are not able to supply the same instances for the out then it doesn't look like you will be able to get a mock to pass successfully. Take a look a the Quick Start for Moq to get an understanding of it capabilities.

How do I mock local variables of a mocked class in a Spock test?

Let's say I have the following method in a service:
private void deleteItems(List<Item> itemsToDelete) {
def sql = new Sql(dataSource)
itemsToDelete?.each { Item item ->
sql.execute("DELETE FROM owner_item WHERE item_id = ${item.id}")
item.delete(flush: true, failOnError: true)
flushDatabaseSession();
}
}
How do I create a test for this method in the ItemServiceSpec? When I try it, I get either a DataSource "Must specify a non-null Connection" error or a nullPointerException on sql.
This is my existing test.
#TestFor(ItemService)
#Mock([Item])
#Build([Item])
class SubjectServiceSpec extends Specification {
...
def "delete items"() {
given:
Item item1 = Item.build().save(flush: true)
Item item2 = Item.build().save(flush: true)
Item.count() == 2
DataSource mockDataSource = Mock()
service.dataSource = mockDataSource
1 * deleteItems
when:
service.deleteItems([item1, item2])
then:
Item.count() == 0
}
}
What you are trying to do here, is to mock a dependency (DataSource) of a dependency (Sql). This normally leads to a situation, where you a not 100% aware of how the Sql interacts with the DataSource Object. If Sql changes private interaction with the Datasource in a Version Update, you have to deal with the situation.
Instead of mocking a dependency of a dependency you should the Sql Class directly. For this, the sql has to be some kind of explicit dependency that you can receive via DI or a method parameter. In this case you can just mock the execute call like so (choosen the way of a Expando-Mock, but you could also use Map or the Mock Stuff from Spock):
given:
def sqlMock = new Expando()
sqlMock.execute = { return 'what ever you want or nothing, because you mock a delete operation' }
service.sql = sqlMock
when:
service.deleteItems([item1, item2])
then:
assertItemsAreDeletedAndTheOwnerAsWell()
Thinking about the whole testcase, there a two major problems in my opinion.
The first one is, when you ask yourself what kind of certainty do you really get here by mocking out the whole sql stuff. In this case, the only thing that you are doing here is to interact with the db. When you mock this thing out, then there is nothing anymore that you could test. There is not many conditional stuff or anything that should be backed up by a unit test. Due to this, I would suggest to write only integration spec for this test-case where you have something like a H2DB for testing purposes inplace.
The second thing is, that you actually don't need the Sql Manipulation at all. You can configure GORM and Hibernate in a way do a automatic and transparent deletion of the owner of the item, if the item is deleted. For this, look at the docs (especially the cascade part) from GORM or directly in the Hibernate docs.
To sum it up: use cascade: 'delete' together with a proper integration test and you have a high amount of certainty and less boilerplate code.

Grails testing user role custom validation constraints using Spring Security Core Plugin

I'm sure this is a fairly common situation. I'm using the Spring Security Core plugin and want to create a domain model that has a Person limited to certain roles:
class Workgroup {
Person manager
...
static constraints = {
manager(validator: {mgr ->
// it feels like there should be a more elegant, groovy way of doing this.
def auths = mgr.getAuthorities();
def returny = false
auths.each {
if(it.authority == 'ROLE_MANAGER')
{
returny = true
}
}
return returny
})
}
}
This test fails like a mofo:
void testInvalidManager() {
def nick = new Person(username:'Nick')
def nonManagerRole = new Role(authority:'ROLE_EMPLOYEE')
UserRole.create(nick,nonManagerRole)
def awesome = new Workgroup(name:'mooCows', manager:nick)
mockForConstraintsTests(Workgroup, [awesome])
assertFalse awesome.validate()
assertEquals "validator", awesome.errors["manager"]
}
testInvalidManager Error No signature of method: users.UserRole.save() is applicable for argument types: (java.util.LinkedHashMap) values: [[flush:false, insert:true]] Possible solutions: wait(), any(), wait(long), use([Ljava.lang.Object;), isCase(java.lang.Object), each(groovy.lang.Closure)
groovy.lang.MissingMethodException: No signature of method: users.UserRole.save() is applicable for argument types: (java.util.LinkedHashMap) values: [[flush:false, insert:true]]
Possible solutions: wait(), any(), wait(long), use([Ljava.lang.Object;), isCase(java.lang.Object), each(groovy.lang.Closure)
at users.UserRole.create(UserRole.groovy:32)
at users.UserRole.create(UserRole.groovy)
at users.UserRole$create.call(Unknown Source)
at users.WorkgroupTests.testInvalidManager(WorkgroupTests.groovy:17)
Is this better covered in Integration than Unit Testing? Do I need to mock UserRole (if so, how?)? How are these types of tests normally done?
UserRole.create() calls save(), so you need to use mockDomain() instead of just mockForConstraintsTests().
But that's only if you're ok with testing the domain model with mocks, which I would never do. The mocking support in Grails should be used when testing Controllers or other classes that use domain classes but shouldn't be bothered with real persistence, creating a database (even in-memory), etc. By removing that dependency you're concentrating on the current tier, trusting that the other tier is already properly tested. But when you use mocking to test domain classes, you're really just testing the mocking framework. So I always use integration tests for domain classes so they run against a real database.
To answer the implicit question from your code example, I'd write the constraint as
static constraints = {
manager validator: { mgr ->
mgr.authorities.find { it.authority == 'ROLE_MANAGER' } != null
}
}
The issue with its bulk is that you're using each() when a regular for loop would be preferable since you can return from a for loop. Use each() only when you really want to invoke the closure on every instance. Here's one that's less groovy than the other one but uses a for loop:
static constraints = {
manager validator: { mgr ->
for (auth in mgr.getAuthorities()) {
if (it.authority == 'ROLE_MANAGER') {
return true
}
}
return false
}
}

First Unit Tests! ASP.NET MVC with Repositories causing errors

I am very new to Unit Testing, so I am starting on my first set of tests today. I am using the Library JustMock from Telerik. Though any unit testing information is good. I am having a bit of trouble with an interface service that passes through my method. Below is my MembershipController.Register(model) method...
[CaptchaValidator]
[HttpPost]
public ActionResult Register(Models.Membership.Registration model)
{
// just for good mesure, truncate any spaces that are given
System.Text.RegularExpressions.Regex.Replace(model.Email, #"\s", "");
if (ModelState.IsValid)
{
// Attempt to register the User and return any applicable status that has to do
// with the result.
var createStatus = membershipService.RegisterMember(model.Email, model.Password);
// if the member is able to be created successfully, log them in now and begin the
// authentication portion of the registration, otherwise, display the registration
// errors and return to the view.
if (createStatus == Membership.MemberCreateStatus.Success)
{
formsAuthentication.SignIn(model.Email, false /* createPersistentCookie */);
return RedirectToAction("Success");
}
else
{
ModelState.AddModelError("", Membership.Validator.ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
And here is the paltry test I am trying to run...
[TestMethod]
public void Register_Post_ReturnsRedirectOnSuccess()
{
// Arrange
var controller = Mock.Create<Web.Controllers.MembershipController>();
var repository = Mock.Create<Membership.IMembershipService>();
Mock.Arrange(() => repository.RegisterMember("acceptible#email.com", "acceptiblePassword")).Returns(Membership.MemberCreateStatus.Success);
// Model
var model = new Web.Models.Membership.Registration
{
Email = "acceptible#email.com",
Password = "acceptiblePassword",
ConfirmPassword = "acceptiblePassword"
};
// Act
var result = controller.Register(model);
// Assert
Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));
}
The test fails because membershipService is resolving as null. I'm not sure what to do here. This is my first forray into the Unit Testing aspect of ASP.NET MVC. Can anyone give me some advice?
I am using Ninject to inject IMembershipService through the Constructor. It is implemented by the class MembershipService. The code runs fine when I run it, but the unit tests fail.
I don't see you passing repository anywhere into your controller. Normally you would have IMembershipService as a parameter in your controller's constructor that you can then pass in when needed or use MVC's Service Locator to grab the Ninject instance and pass it in.
:)