{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/orleans/issues/362","labels_url":"https://api.github.com/repos/dotnet/orleans/issues/362/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/orleans/issues/362/comments","events_url":"https://api.github.com/repos/dotnet/orleans/issues/362/events","html_url":"https://github.com/dotnet/orleans/issues/362","id":71417903,"number":362,"title":"Make the Grain class unit testable","user":{"login":"nehmebilal","id":11776504,"avatar_url":"https://avatars.githubusercontent.com/u/11776504?v=3","gravatar_id":"","url":"https://api.github.com/users/nehmebilal","html_url":"https://github.com/nehmebilal","followers_url":"https://api.github.com/users/nehmebilal/followers","following_url":"https://api.github.com/users/nehmebilal/following{/other_user}","gists_url":"https://api.github.com/users/nehmebilal/gists{/gist_id}","starred_url":"https://api.github.com/users/nehmebilal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nehmebilal/subscriptions","organizations_url":"https://api.github.com/users/nehmebilal/orgs","repos_url":"https://api.github.com/users/nehmebilal/repos","events_url":"https://api.github.com/users/nehmebilal/events{/privacy}","received_events_url":"https://api.github.com/users/nehmebilal/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/dotnet/orleans/labels/enhancement","name":"enhancement","color":"84b6eb"},{"url":"https://api.github.com/repos/dotnet/orleans/labels/help%20wanted","name":"help wanted","color":"159818"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":27,"created_at":"2015-04-27T22:20:18Z","updated_at":"2015-05-11T19:26:02Z","closed_at":null,"body":"## Problem\r\nIn the current version of Orleans, I faced two issues when writing unit tests. \r\n\r\n1. The GrainFactory is static and cannot be mocked or stubbed in a simple way. \r\n2. The GetPrimaryKey(),GetPrimaryKeyLong() and GetPrimaryKeyString() methods are also difficult to mock/stub for more than one reason (one of them being that they are extension methods).\r\n\r\nFor this reason, it is not possible to test Orleans grains without starting a Silo. There are many disadvantages of relying on a Silo:\r\n\r\n1. The class under test is not completely isolated because some of the code being tested is communicating with the Orleans runtime.\r\n2. It's not possible to cover all branches/lines of code because there is no simple way to tell the Silo to fail or throw a specific exception (which is possible with mocks).\r\n3. The tests are slower to run and harder to setup.\r\n\r\n## Proposal\r\nTo solve the above issues, I added two properties to the Grain class:\r\n```csharp\r\n        public IGrainLocator GrainLocator { get; set; }\r\n        public IGrainIdentifier Identifier { get; set; }\r\n```\r\n\r\nThe default implementations simply forward the calls to the existing **GrainFactory** and extensions methods respectively. During unit testing, the default implementations can be simply replaced with mock objects using constructor or setter injection. The **IGrainLocator** and **IGrainIdentifier** interfaces are as follows.\r\n\r\n```csharp\r\n    public interface IGrainLocator\r\n    {\r\n        TGrainInterface GetGrain<TGrainInterface>(Guid primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithGuidKey;\r\n        TGrainInterface GetGrain<TGrainInterface>(long primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithIntegerKey;\r\n        TGrainInterface GetGrain<TGrainInterface>(string primaryKey, string grainClassNamePrefix = null) where TGrainInterface : IGrainWithStringKey;\r\n    }\r\n\r\n    public interface IGrainIdentifier\r\n    {\r\n        Guid AsGuid();\r\n        long AsLong();\r\n        string AsString();\r\n    }\r\n```\r\n\r\n## Usage\r\nHere is an example that shows how the new Api can be used during unit testing.\r\n\r\nIn this example, we have a **DeviceGrain** and a **PrefsGrain**. The **DeviceGrain** uses the **PrefsGrain** to save it’s preferences. Assume that the device has a screen and one of its prefs is the screen background color.\r\n\r\n```csharp\r\n    public class DeviceGrain : Grain, IDeviceGrain\r\n    {\r\n        private const string BackgroundColor = \"background_color\";\r\n\r\n        public Task<string> GetScreenBackgroundColor()\r\n        {\r\n            return GetPrefsGrain().GetValue(BackgroundColor);\r\n        }\r\n\r\n        public Task SetBackgroundColor(string color)\r\n        {\r\n            return GetPrefsGrain().SetValue(new KeyValuePair<string, string>(BackgroundColor, color));\r\n        }\r\n\r\n        private IPrefsGrain GetPrefsGrain()\r\n        {\r\n            return GrainLocator.GetGrain<IPrefsGrain>(Identifier.AsString());\r\n        }\r\n    }\r\n```\r\nNotice that the `GetPrefsGrain()` is using the new API to communicate with the prefs grain and to find its **Id** (of course the current Orleans **GrainFactory** still works for backward compatibility). Below a unit test for the `GetScreenBackgroundColor()` method (I used **Moq** for mocking here):\r\n\r\n```csharp\r\n        [TestMethod]\r\n        public void TestGetScreenBackgroundColor()\r\n        {\r\n            var prefsGrainMock = new Mock<IPrefsGrain>();\r\n            prefsGrainMock.Setup(prefs => prefs.GetValue(\"background_color\")).Returns(Task.FromResult(\"black\"));\r\n\r\n            var grainLocatorMock = new Mock<IGrainLocator>();\r\n            grainLocatorMock.Setup(grainLocator => grainLocator.GetGrain<IPrefsGrain>(\"deviceId\", null)).Returns(prefsGrainMock.Object);\r\n\r\n            var grainIdentifierMock = new Mock<IGrainIdentifier>();\r\n            grainIdentifierMock.Setup(identifier => identifier.AsString()).Returns(\"deviceId\");\r\n\r\n            DeviceGrain device = new DeviceGrain() {GrainLocator = grainLocatorMock.Object, Identifier = grainIdentifierMock.Object};\r\n            Assert.AreEqual(\"black\", device.GetScreenBackgroundColor().Result);\r\n        }\r\n```\r\n\r\nAs you can see in the example above, it is very easy to replace the **IGrainLocator** and **IGrainIdentifier** with mocks during unit testing. In addition, using properties instead of a static class or extension methods makes it possible to use **dependency injection** to replace the grain factory and identifier. As a result, with rather simple changes, the unit testing experience of Orleans grains is much better.\r\n\r\n## Pull Request\r\n#363 "},"comment":{"url":"https://api.github.com/repos/dotnet/orleans/issues/comments/101024414","html_url":"https://github.com/dotnet/orleans/issues/362#issuecomment-101024414","issue_url":"https://api.github.com/repos/dotnet/orleans/issues/362","id":101024414,"user":{"login":"veikkoeeva","id":5158694,"avatar_url":"https://avatars.githubusercontent.com/u/5158694?v=3","gravatar_id":"","url":"https://api.github.com/users/veikkoeeva","html_url":"https://github.com/veikkoeeva","followers_url":"https://api.github.com/users/veikkoeeva/followers","following_url":"https://api.github.com/users/veikkoeeva/following{/other_user}","gists_url":"https://api.github.com/users/veikkoeeva/gists{/gist_id}","starred_url":"https://api.github.com/users/veikkoeeva/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/veikkoeeva/subscriptions","organizations_url":"https://api.github.com/users/veikkoeeva/orgs","repos_url":"https://api.github.com/users/veikkoeeva/repos","events_url":"https://api.github.com/users/veikkoeeva/events{/privacy}","received_events_url":"https://api.github.com/users/veikkoeeva/received_events","type":"User","site_admin":false},"created_at":"2015-05-11T19:26:02Z","updated_at":"2015-05-11T19:26:02Z","body":"@gabikliot If we have improved DI in the future, would this be \"confusing\" (I can't make up a better word)? I mocking is just one scenario enabled by DI and when that is available, I would use that. Though I wonder about the DI container, if it needs to maintain state, how should it be run in production and even in testing scenarios to test that it works as expected. Perhaps the container ought to be a grain or then host some of the well known ones in a grain.\r\n\r\nBut I point out again there are many styles of development and somehow Orleans should cater for them. I don't have that much bandwidth to make really sound proposals other than offer a point of view (you likely know this, but I state it for being as explicit as I can). I think DI, when we get there and there seems to be consensus something like that is needed, will solve this scenario too. Perhaps one question here is that could mocking be some sort of a spike or initial feature that starts proper DI support."}}