Been a long time user of Subsonic 2.x and have used 3.x a bit, but I've recently started transitioning our use of SS from using repositories to ActiveRecord instead. I'm currently stumbling on some of our unit tests and I wonder if it's perhaps because I'm misunderstanding the intent of the Setup() method. Alas, the only documentation I can find is on Rob Conery's blog.
On my unit tests, I'm stuffing a collection of objects, let's say a List of Accounts. I then want to validate that some code is properly filtering against the repo by a property, let's say the email address. My (simplified) unit test setup is below.
The kicker is that when using the "Test" connection strings, it seems like any LINQ I write against the repo returns me all the records I stuffed into the Setup--which is making me wonder if I'm misunderstanding the intention of Setup(). It's as if it were behaving like a Mock setup, e.g. mymock.Setup(foo => foo.Email).Returns("user#user.com").
List accounts = new List()
{
new Account() { FirstName = "Paul", LastName = "McCartney", Email = "paul#beatles.com" },
new Account() { FirstName = "John", LastName = "Lennon", Email = "john#beatles.com" },
new Account() { FirstName = "Ringo", LastName = "Starr", Email = "ringo#beatles.com" },
new Account() { FirstName = "George", LastName = "Harrison", Email = "george#beatles.com" },
new Account() { FirstName = "Taylor", LastName = "Swift", Email = "immaletyou#finish.com" }
};
DB.Account.ResetTestRepo();
DB.Account.Setup( accounts );
Elsewhere, the code I'm trying to unit test is basically performing a Find(). The real implementation has a semi complex set of conditions, but even simplified conditions don't appear to work.
Account.Find(a => a.Email == "immaletyou#finish.com").SingleOrDefault();
The above will bomb with an exception indicating that the lambda returned multiple elements. When I debug into the test, sure enough, the result of the Find() is all the objects I had stuffed into the mocked repo via the Setup() method.
Rob C laments that ActiveRecord can be tough to test--which is a bummer. But I can't imagine that the testing scenario is breaking on such a mundane sample--it's PEBKAC right?
Halp!?
Edit:
Josh Rivers asks what appears to be a similar question, though it doesn't appear to be resolved. Linking for completeness.
Going to answer my own question for any future parties, not that there's a ground swell of activity rushing to this thread:
Turns out that the current implementation of test repositories (Subsonic 3.03) has a bug where it basically just returns the entire set of values inside the repository. The current fix (haven't tested myself, but has worked for others) is to pull the current main line of the source code and recompile.
See: Subsonic Issue 109
Related
Basically we might have some data like so
open class RealmCart : RealmObject() {
#PrimaryKey
var _id: String = UUID.randomUUID().toString()
var items: RealmList<RealmCartItem> = RealmList()
var discountCode: String? = null
var userId: String = ""
}
And we do not want people editing these by mistake. We have some failsafe like code owners, labels in the repo, but we also want to have a unit test that can also prevent a merge if the data is changed in any way (add, change, or remove data). Basically, we do not want any accidents, and people are not perfect.
What is the best way to go about such a thing?
This is what I ended up doing:
I created an extension function for my data models
fun RealmObject.testDeclaredFields(): List<String> {
val fields = this::class.java.fields.map { it.name }
return this::class.java.declaredFields
.map { it.name }
.filterNot { fields.contains(it) }
.sorted()
}
Basically this just gets the data model fields, excluding things like companion objects.
Then I was able to create a test simply like
class RealmMessageTest {
#Test
fun `RealmMessage fields match spec`() {
val item = RealmMessage().testDeclaredFields()
assertContentEquals(item, fieldSpec)
}
private val fieldSpec = listOf(
"_id",
"acknowledgeStatusValue",
"body",
"completed",
"createdAt",
"deliveryStatusValue",
"from",
"meta",
"organizationId",
"platforms",
"threadId",
"title"
).sorted()
}
Why do this? Sometimes when someone is making changes carelessly, they will not realize that they have added a field, changed a field, or removed an important field in a data model that is sync'd to the backend. This does not prevent the developer from changing it, but given that they need to now change it in two places, they will be more cognizant whether they need to make this change or not.
I noticed a lot of people questioned why you would need to do this. My answer is that, I work in a very large repo where newer developers edit this without a second thought. This is just to make them more cognizant of changes to these important models, before they break develop. There are code owners for the repo, but they may not always see these changes. It is just an extra precaution.
How about using a mechanism like githooks to prevent the editing of certain files from being committed?
I'm not familiar with githooks, so I can't show you exactly how to do it, but I think it would be good to prevent commits and inform the developer of the situation with an error message.
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.
I am using using Effort (for EF4) to do some unit tests.
var ctx= Effort.ObjectContextFactory.CreateTransient<TheContext>(Shared.Connection);
ctx.companies.AddObject(new company() { ID = 100, name = "Agent", is_agent = true });
ctx.SaveChanges(System.Data.Objects.SaveOptions.DetectChangesBeforeSave);
The ID column in the company is an identity field. After executing the above query, it turns out the ID value is 1 instead of 100. Is there any way to control Identity_insert while using Effort
This is just to add the solution on this thread. #MrBlueSky add the archieved links of the solution on the comments above. But, DbConfiguration which is used on the link, does not exists anymore on the latest Effort. The solution still exists on Effort.EF6 [version="1.3.0"]. And you can use the method SetIdentityFields on EffortConnection.DbManager to turn on or off the identity fields
if (Effort.DbConnectionFactory.CreateTransient() is EffortConnection connection)
{
connection.Open();
connection.DbManager.SetIdentityFields(false);
connection.DbManager.ClearMigrationHistory();
connection.Close();
}
Turning on
// Add data with explicitly set id
Person initPerson = new Person { Id = 5, FirstName = "John", LastName = "Doe" };
dataInitContext.People.Add(initPerson);
dataInitContext.SaveChanges();
Assert.AreEqual(5, initPerson.Id);
// Enable identity field
connection.Open();
connection.DbManager.SetIdentityFields(true);
connection.Close();
I am using assigned id in my domain
class Book {
Integer id
String name
static mapping = {
id generator: 'assigned'
}
}
so to add a new book:
def book = new Book([name: "The Adventures of Huckleberry Finn"])
book.id = 123
book.save(flush: true)
everything works perfectly, the problem is in my unit tests
first of all I can only mock 1 domain class,
secondly, I cannot use .save() on unit test, so my only option (as far as i know) is to use mockDomain as follow:
mockDomain(Book, [ [id: 123, name: "The Adventures of Huckleberry Finn"] ])
but it is not working, it would work in a normal domain without "id generator: 'assigned'"
any ideas?
I read that I wouldn't face this problem in integrated test, it is just a problem in unit test
thanks
You would need the bindable constraint for id if you want to use (by default id is not bindable) it as map params to create the domain object in unit test. The domain class would have
static constraints = {
id bindable: true
}
Words of advice:
If you are using Grails > 2.x, use #Mock to mock domain classes instead of mockDomain. You can find details about Unit Testing in Grails docs.
Another Level Up
Use build-test-data plugin to mock domain objects.
This solution fits my needs:
Book mockBook = [name: "The Adventures of Huckleberry Finn"] as Book
mockBook.metaClass.id = 123
assert mockBook.id == 123
I have been using NBuilder for a while in unit tests to simulate in-memory data and it's awesome, then I wanted to use it to test my NHibernate mappings, I thought it was going to be transparent but I can not figure out what I am doing wrong =( it is simply not working
I am planing to test heavily my NHibernate mapping but since I have too many entities I do not want to populate data manually, that's the main reason I want to use NBuilder
just as a quick reference:
autoConfig.Override<Planet>(x =>
{
x.References(y => y.Sun).Cascade.SaveUpdate().Column("Star_id");
});
autoConfig.Override<Star>(y =>
{
y.HasMany(x => x.Planets).Inverse().Cascade.AllDeleteOrphan();
});
(If you need I can provide information about the entities and the mappings but I think they are correct since i am able to save my entities when the data is populated manually)
Manually:
using (var session = factory.OpenSession())
using (var tran = session.BeginTransaction())
{
var star = new Star { Class = StarTypes.B, Color = SurfaceColor.Red, Mass = 323.43, Name = "fu..nny star" };
star.Planets = new List<Planet>
{
new Planet { IsHabitable = true, Name = "my pla", Sun = star }
};
session.Save(star);
tran.Commit();
}
The above code actually works saving both entities to the database correctly meaning that my mappings are correct but now I want to use NBuilder to auto populate testing data like this:
var star = Builder<Star>.CreateNew().Build();
star.Planets = Builder<Planet>.CreateListOfSize(10).All().With(x => x.Sun, star).Build();
session.Save(star);
tran.Commit();
Inspecting the generated entities while debugging look correct to me, I can navigate through them without problems, but then when I want to commit the transaction I get the following error:
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [CH9_NHibernateLinqToNHibernate.Domain.Planet#00000000-0000-0000-0000-000000000001]
Any thoughts?
I found the problem, basically NBuilder was assigning a value to my Id and NHibernate was considering it 'persisted', and it was trying to update the record instead of create a new one (the error message was not helping me though...):
var star = Builder<Star>.CreateNew().Build();
star.Planets = Builder<Planet>.CreateListOfSize(10).All().With(x => x.Sun, star).With(x => x.Id, Guid.Empty).Build();