i have a test where i will be comparing two objects.
i am open to know whats the best way to do it.
i have created something for which i have an issue that needs help.
following code has an object property that needs to be present
i would like to assert that all fields to be present except the id property.
i feel like the last 5 statements feel inappropriate, if there is a clearer way of doing it, i would like to know.
[Fact]
public void CreateTransaction_AddFirstTransaction_ShouldUpdateTransactionJson()
{
// Arrange
var mockFileSystem = new MockFileSystem();
var buyCrypto = new BuyCrypto(mockFileSystem);
var bitcoin = new Currency()
{
name = "bitcoin",
code = "btc",
price = 10
};
// Act
buyCrypto.CreateTransaction(true, bitcoin, 10);
//Assert
var result = JsonSerializer
.Deserialize<List<Transaction>>(mockFileSystem.GetFile(TransactionJson).TextContents);
Assert.Equal("bitcoin", result[0].currency);
Assert.Equal(DateTime.Now.ToString(), result[0].dateTime);
Assert.Equal("TestName", result[0].name);
Assert.Equal(10, result[0].quantity);
Assert.Equal(100, result[0].total);
}
I love using Fluent Assertions for these kinds of tests (docs). You could do something like this:
// Arrange
// ... other stuff
var expectedTransaction = new Transaction {
currency = "bitcoin",
dateTime = DateTime.Now.ToString(),
name = "TestName",
quantity = 10,
total = 100 };
// Act
// ...
// Assert
result[0].Should().BeEquivalentTo(expectedTransaction, options => options.Excluding(t => t.Id));
Related
I have a mocked executor that Asserts the unit after x amount of callbacks depending on what values we give to the parameters. Here's a sample code of my unit test
[Test]
[TestCase("foo", false, 2)]
[TestCase("foo", true, 3)]
public void CommandLineShouldBeValidTest(string parameter1, bool parameter2, int finalCallbackCounter)
{
int initialCallbackCounter = 0;
var executorMock = new Mock<ITaskExecutor>();
executorMock.Setup(m => m.Execute(It.IsAny<IExecutionContext>(), It.IsAny<ITask>()))
.Callback<IExecutionContext, ITask>((c, it) =>
{
var p = (ProcessTask)it;
initialCallbackCounter++;
if (initialCallbackCounter == finalCallbackCounter)
{
Assert.AreEqual(expectedCommandLine, p.CommandLine);
}
});
var macro = new Macro(parameter1, parameter2, executorMock.Object);
macro.Execute();
}
For the moment I'm using the finalCallbackCounter parameter for it, so for example if I make the second boolean parameter true instead of false, I need to change it to 3 (I actually have more arguments and cases in my current code, I just simplified it for the question's purpose).
This way to do it feels really finnicky and not very futureproof. Is there a more elegant solution to this problem?
You could capture the ITasks, then assert at the end.
Setup is not intended for assertions:
var capturedTasks = new List<ITask>();
var executorMock = new Mock<ITaskExecutor>();
executorMock.Setup(m => m.Execute(
It.IsAny<IExecutionContext>(),
Capture.In(capturedTasks)));
var macro = new Macro(mock.Object);
macro.Execute();
if (capturedTasks.Count >= finalCallbackCounter)
{
var p = (ProcessTask)capturedTasks[finalCallbackCounter - 1];
Assert.AreEqual(expectedCommandLine, p.CommandLine);
}
This may not be something that's even possible but I thought I'd ask anyway. Is there anyway for me to stub out this method so that the second call is also stubbed out using the parameter provided in the method I'm testing?
The method to stub:
public SupportDetails GetSupportDetails(string languageKey)
{
var result = FindSupportDetails(languageKey);
return result ?? FindSupportDetails("en-us");
}
My Current test:
public void GetsUSDetails_IfLangKeyDoesNotExist()
{
var langKey = "it-it";
_repo.Stub(s => s.FindSupportDetails(langKey))
.Return(supportDetails.Where(sd => sd.LanguageKey == langKey)
.SingleOrDefault());
ISupportRepository repo = _repo;
var actual = repo.GetSupportDetails(langKey);
Assert.AreEqual("en-us", actual.LanguageKey);
}
and the supportDetails object used in the test:
supportDetails = new SupportDetails[]
{
new SupportDetails()
{
ContactSupportDetailsID = 1,
LanguageKey = "en-us"
},
new SupportDetails()
{
ContactSupportDetailsID = 2,
LanguageKey = "en-gb"
},
new SupportDetails()
{
ContactSupportDetailsID = 3,
LanguageKey = "es-es"
}
};
The correct and the most elegant solution to your problem is to use Do method:
_repo.Stub(s => s.FindSupportDetails(null))
.IgnoreArguments()
.Do((Func<string, SupportDetails>)
(langKey => supportDetails.SingleOrDefault(sd => sd.LanguageKey == langKey)));
The Func will raise no matter what argument was passed to FindSupportDetails, then the correct SupportDetails will select.
MongoDB was harder than I remembered! I've tried various versions of if-exists-replace-else-insert with various functions and options. It should be easy, shouldn't it?
It's my personal opinion that the following should work.
var collection = storageClient.GetCollection<Observer>("observers");
await collection.Indexes.CreateOneAsync(Builders<Observer>.IndexKeys.Ascending(_ => _.MyId), new CreateIndexOptions { Unique = true });
foreach (var observer in _observers)
{
observer.Timestamp = DateTime.Now;
var res = await collection.FindAsync(o => o.MyId == observer.MyId);
if (res==null ||res.Current == null) {
await collection.InsertOneAsync(observer); //Part 1, fails 2nd time solved with res.MoveNextAsync()
}
else
{
observer.ID = res.Current.Single().ID;
var res2 = await collection.ReplaceOneAsync(o=>o.MyId==observer.MyId, observer);
var res3 = await collection.FindAsync(o => o.MyId == observer.MyId);
await res3.MoveNextAsync();
Debug.Assert(res3.Current.Single().Timestamp == observer.Timestamp); //Part 2, Assert fails.
}
}
Observer looks approximately like this:
public class Observer : IObserver
{
[BsonId]
public Guid ID { get; set; }
public int MyId { get; set; }
public DateTime Timestamp { get; set; }
}
The second time I run this with the exact same collection I unexpectedly get:
E11000 duplicate key error index: db.observers.$MyId_1 dup key: { : 14040 }
Edit:
Added original part two code: replacement.
Edit 2:
Now my code looks like this. Still fails.
var collection = storageClient.GetCollection<Observer>("gavagai_mentions");
await collection.Indexes.CreateOneAsync(Builders<Observer>.IndexKeys.Ascending(_ => _.MyID), new CreateIndexOptions { Unique = true });
foreach (var observer in _observers)
{
observer.Timestamp = DateTime.Now;
// Create a BsonDocument version of the POCO that we can manipulate
// and then remove the _id field so it can be used in a $set.
var bsonObserver = observer.ToBsonDocument();
bsonObserver.Remove("_id");
// Create an update object that sets all fields on an insert, and everthing
// but the immutable _id on an update.
var update = new BsonDocument("$set", bsonObserver);
update.Add(new BsonDocument("$setOnInsert", new BsonDocument("_id", observer.ID)));
// Enable the upsert option to create the doc if it's not found.
var options = new UpdateOptions { IsUpsert = true };
var res = await collection.UpdateOneAsync(o => o.MyID == observer.MyID,
update, options);
var res2 = await collection.FindAsync(o => o.MyID == observer.MyID);
await res2.MoveNextAsync();
Debug.Assert(res2.Current.Single().Timestamp == observer.Timestamp); //Assert fails, but only because MongoDB stores dates as UTC, or so I deduce. It works!!
}
You can do this atomically with UpdateOneAsync by using the IsUpsert option to create the doc if it doesn't already exist.
foreach (var observer in _observers)
{
// Create a BsonDocument version of the POCO that we can manipulate
// and then remove the _id field so it can be used in a $set.
var bsonObserver = observer.ToBsonDocument();
bsonObserver.Remove("_id");
// Create an update object that sets all fields on an insert, and everthing
// but the immutable _id on an update.
var update = new BsonDocument("$set", bsonObserver);
update.Add(new BsonDocument("$setOnInsert", new BsonDocument("_id", observer.ID)));
// Enable the upsert option to create the doc if it's not found.
var options = new UpdateOptions { IsUpsert = true };
var res = await collection.UpdateOneAsync(o => o.MyId == observer.MyId,
update, options);
}
Ok, it's just been a long time since I worked with cursors.
await res.MoveNextAsync();
helped.
This stuff is not hard to Google, but the fact of the matter is, I failed, so I'm going to leave the question up.
If you have an answer for part two https://stackoverflow.com/questions/32586064/replace-poco-with-mongodb-net-driver-2, then and would really like to post it here then I'll be happy two edit the questions.
As was pointed out in the comments the information is in fact readily available in the docs http://mongodb.github.io/mongo-csharp-driver/2.0/reference/driver/crud/reading/#finding-documents.
I have method that looks like this:
private static IEnumerable<OrganizationViewModel> GetOrganizations()
{
var db = new GroveDbContext();
var results = db.Organizations.Select(org => new OrganizationViewModel
{
Id = org.OrgID,
Name = org.OrgName,
SiteCount = org.Sites.Count(),
DbSecureFileCount = 0,
DbFileCount = 0
});
return results;
}
This is returns results pretty promptly.
However, you'll notice the OrganizationViewModel has to properties which are getting set with "0". There are properties in the Organization model which I added via a partial class and decorated with [NotMapped]: UnsecureFileCount and SecureFileCount.
If I change those 0s to something useful...
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount
... I get the "Only initializers, entity members, and entity navigation properties are supported" exception. I find this a little confusing because I don't feel I'm asking the database about them, I'm only setting properties of the view model.
However, since EF isn't listening to my argument I tried a different approach:
private static IEnumerable<OrganizationViewModel> GetOrganizations()
{
var db = new GroveDbContext();
var results = new List<OrganizationViewModel>();
foreach (var org in db.Organizations)
{
results.Add(new OrganizationViewModel
{
Id = org.OrgID,
Name = org.OrgName,
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount,
SiteCount = org.Sites.Count()
});
}
return results;
}
Technically this gives me the correct results without an exception but it takes forever. (By "forever" I mean more than 60 seconds whereas the first version delivers results in under a second.)
Is there a way to optimize the second approach? Or is there a way to get the first approach to work?
Another option would be to load the values back as an anonymous type and the loop through those to load your viewmodel (n+1 is most likely the reason for the slowness).
For example:
var results = db.Organizations.Select(org => new
{
Id = org.OrgID,
Name = org.OrgName,
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount,
SiteCount = org.Sites.Count()
}).ToList();
var viewmodels = results.Select( x=> new OrganizationViewModel
{
Id = x.Id,
Name = x.Name,
DbSecureFileCount = x.DbSecureFileCount,
DbFileCount = x.DbFileCount,
SiteCount = x.SiteCount
});
Sorry about the formatting; I'm typing on a phone.
You are basically lazy loading each object at each iteration of the loop, causing n+1 queries.
What you should do is bring in the entire collection into memory, and use it from there.
Sample code:
var organizationList = db.Organizations.Load();
foreach (var org in organizationList.Local)
{
//Here you are free to do whatever you want
}
I have an IRecord which contains an ICollection of Samples. The ICollection looks like this:
Sample sample1 = scope.DbContext.Samples.AddNew(new Sample
{
Name = GenerateName("Sample one"),
Tests = tests
});
Sample sample2 = scope.DbContext.Samples.AddNew(new Sample
{
Name = GenerateName("Sample two"),
Tests = tests
});
ICollection<Sample> samples = new Collection<Sample>();
samples.Add(sample1);
samples.Add(sample2);
Then I add the samples to the record:
Order record = scope.DbContext.Orders.AddNew(new Order
{
Name = GenerateName("Order"),
Samples = samples
});
Now I want to get the Samples out of the record. I know that when I do
object name = record["Name"];
I get the correctly generated name. However, when I do
object propertyValue = record["Samples"];
it has no items in it. I want to do the following:
object propertyValue = record["Samples"];
if (typeof(IEnumerable<IRecord>).IsAssignableFrom(propertyValue.GetType()))
{
foreach (var property in (IEnumerable<IRecord>)propertyValue)
{
var test = property;
}
}
So why does record["Samples"] not get the ICollection?
It looks to me, that what you really want to use is the as keyword.
var samples = record["Samples"] as ICollection<Sample>;
However, why aren't you just using the CLR static type?
var samples = record.Samples;
Maybe I am just not understanding what you are trying to do here, but it seems like it can be boiled pretty simply to the above.