Urbi
Urbi

Reputation: 125

I am trying to write Unit Test in C# for static methods

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

Answers (4)

Gregory Prescott
Gregory Prescott

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

Scott Hannen
Scott Hannen

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

Nkosi
Nkosi

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

Alejandro
Alejandro

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

Related Questions