Your method has 3 responsibilities:
- management object searcher creation
- os and architecture retrieval
- os verification
To enable testing I'd add in AssemblyInfo.cs a line similar to this:
[assembly: InternalsVisibleTo("Your.Test.Project")]
so that the protected methods will be visible to the test project but not freely available to clients.
Then I'd rewrite your method so that the os verification is separated:
//Acceptance test this method
public static bool Is32BitOS()
{
var managementObjects = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem").Get().OfType<ManagementObject>();
string os = (from x in managementObjects select x.GetPropertyValue("Caption")).First().ToString().Trim();
string architecture = (from x in managementObjects select x.GetPropertyValue("OSArchitecture")).First().ToString();
return Is32BitOS(os, architecture);
}
//Unit test this method
internal static bool Is32BitOS(string os, string architecture)
{
if (os.Equals("Microsoft Windows XP Professional"))
{
return true;
}
if (os.StartsWith("Microsoft Windows 7"))
{
string architecture = archRetriever();
if (architecture == "64-bit")
{
return false;
}
}
return true;
}
Now that we have separated concerns I'd say that your Unit Test should only verify the Is32BitOs behaviour while an Acceptance Test should verify the end to end stack.
In fact you have little value in unit testing that
(from x in new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem").Get().OfType<ManagementObject>()
select x.GetPropertyValue("Caption")).First().ToString().Trim();
retrieves the os string while your real value resides in the usage of this info to determine if the Os is 32 bit.
An alternative to wrap things up in interfaces and use mocking frameworks is to apply functional programming look here for Llewellyn Falco's peel and slice technique and here Arlo Belshee's no mocks approach. So instead of a method like:
public static bool Is32BitOS(IRetrieveOsAndArchitecture roa)
{
string os = roa.GetOs();
string architecture = roa.GetArchitecure();
return Is32BitOS(os, architecture);
}
You could use something like:
public static bool Is32BitOS(Func<ManagementObjectSearcher, string> osRetriever,
Func<ManagementObjectSearcher, string> archRetriever,
ManagementObjectSearcher searcher)
{
string os = osRetriever(searcher);
string architecture = archRetriever(searcher);
return Is32BitOS(os, architecture);
}
Its client method will be:
public static bool Is32BitOS()
{
var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem");
return Is32Bit((s)=>{ (from x in s.Get().OfType<ManagementObject>() select x.GetPropertyValue("Caption")).First().ToString().Trim()},
(s)=>{ (from x in s.Get().OfType<ManagementObject>() select x.GetPropertyValue("OSArchitecture")).First().ToString();},
searcher);
}
Notice that while testing the cases of interfaces and functional you are not using the real ManagementObjectSearcher external dependency; in mockist approach you'll use a mock object in functional programming you'll pass in a different lambda expression which should return only the os and architecture strings.
There's still a responsibility left to this method and is the creation of the ManagementObjectSearcher; to resolve this dependency you can:
- pass it as the parameter of the method
- make it a field of your class initilized at construction time