Reputation: 125
I am learning unit test for c# web application. I am stuck in above mention scenario. I am not sure if I am doing it in correct way. I have FakePath class for unit test. How do I write unit test for static method,Abc.log(), in MSTest?
public class Abc
{
public static void log(string msg)
{
//Read on Write on path;
string path = getPath(new ServerPath());
}
public static string getPath(IServerPath path)
{
return path.MapPath("file.txt");
}
}
interface IServerPath()
{
string MapPath(string file);
}
class ServerPath : IServerPath
{
string MapPath(string file)
{
return HttpContext.Current.Server.MapPath(file);
}
}
class FakeServerPath : IServerPath
{
string MapPath(string file)
{
return @"C:\"+file;
}
}
Upvotes: 2
Views: 6114
Reputation: 574
You are trying to test a void method, so one of your options of asserting this method is to verify if the method is called:
string expectedStr = "c:\file.txt";
[TestMethod]
public void FakeServerPath_VerifyMapPathWasCalled()
{
var fakeServerPath = Isolate.Fake.NextInstance<ServerPath>();
Isolate.WhenCalled(() => fakeServerPath.MapPath("")).WillReturn(expectedStr);
Abc.log("");
Isolate.Verify.WasCalledWithExactArguments(() => fakeServerPath.MapPath("file.txt"));
}
Another option is to test the return value of the getPath(IServerPath path)
method, by modifying the return value of ServerPath's
MapPath(string file)
method to return a wanted value, and to assert if the return value is as expected.
string expectedStr = "c:\file.txt";
[TestMethod]
public void ModifyReturnValueFromMapPath_IsEqualToExpactedStr()
{
var fakeServerPath = Isolate.Fake.NextInstance<ServerPath>();
Isolate.WhenCalled(() => fakeServerPath.MapPath("")).WillReturn(expectedStr);
var result = Abc.getPath(fakeServerPath);
Assert.AreEqual(expectedStr, result);
}
Notice that by using TypeMock Isolator you will be able to fake the future instance of "ServerPath" without changing your original code.
And if necessary TypeMock is also able to mock HttpContext
class as so:
string expectedStr = "c:\file.txt";
[TestMethod]
public void ModifyReturnValueFromHttpContext_IsEqualToExpactedStr()
{
var serverPath = new ServerPath();
Isolate.WhenCalled(() => HttpContext.Current.Server.MapPath("")).WillReturn(expectedStr);
var result = Abc.getPath(serverPath);
Assert.AreEqual(expectedStr, result);
}
Upvotes: 2
Reputation: 29207
Here's a simple approach using dependency injection.
public class FileLogger
{
private readonly string _filePath;
public FileLogger(string filePath)
{
_filePath = filePath;
}
public void Log(string msg)
{
//write to the log
}
}
Now your logging class has a single responsibility - writing to the file. It is not responsible for figuring out which file to write to. It expects to have that value injected into it.
Also, I avoided using a static
method. If the method is static
then a similar problem occurs again: How do you test classes that depend on the logging class?
By making it non-static you can repeat the same pattern - mock the logger so that you can test classes that depend on it.
public interface ILogger
{
void Log(string msg);
}
Then your logging class can implement the interface and you can inject that interface (instead of a concrete class) into classes that might need to write to a log.
Here's a post which demonstrates injecting a logger into a class. There are often better ways to accomplish the same purpose (like using an interceptor) but at least it prevents your code from having dependencies everywhere on some concrete class.
Upvotes: 0
Reputation: 246998
In a case like this you need to expose a way to set the dependency. Currently you are using a new ServerPath()
directly in the method which makes it difficult to inject your FakeServerPath
for testing.
You can modify Abc
public class Abc
{
static Abc() { ServerPath = new ServerPath(); }
public static IServerPath ServerPath { get; set; }
public static void log(string msg) {
//Read on Write on path;
string path = getPath(ServerPath);
}
public static string getPath(IServerPath path) {
return path.MapPath("file.txt");
}
}
And a test could look like this
[TestMethod]
public void Abc_log_Test() {
//Arrange
string filename = "fakeFile.txt";
string expected = @"C:\" + filename;
var mockServerPath = new Mock<IServerPath>();
mockServerPath
.Setup(m => m.MapPath(filename))
.Returns(expected)
.Verifiable();
Abc.ServerPath = mockServerPath.Object;
var message = "Hello world";
//Act
Abc.log(message);
//Assert
mockServerPath.Verify();
}
Note I used Moq to mock up the server path
Upvotes: 0
Reputation: 7813
Testing statics in itself is easy, just call them and assert on the results (the actual problem is testing code that uses statics, as they can't be mocked out). Testing in this particular case is complicated by a different thing.
The real problem of the Log
method is that ti creates a ServerPath
instance by itself, which precludes any chance of Dependency Injection (on the contrary, the GetPath
method is totally friendly to testing, as it takes an interface as a parameter).
I would introduce a refactoring on the Abc
class to enable better testing on it. I would modify it as such:
public static class Logger
{
public static void Log(IServerPath path, string msg)
{
//add parameter checking here
string path = path.MapPath("file.txt");
//actual log goes here
}
}
Note that now the test would be responsible to create the IServerPath
instance, which can then use to inject the mock
Upvotes: 0