GitXplorerGitXplorer
j

StaticMocks

public
11 stars
0 forks
2 issues

Commits

List of commits on branch master.
Unverified
a434b0b9d89f85c12bf038e8bc093f08fdbedef2

Update README.md

jjcansdale committed 8 years ago
Unverified
4b8cdf54b667451c1597c4c90e6712fd68247a74

Changed to RC.

jjcansdale committed 8 years ago
Unverified
5e78ed45e8037ce377f48a9bef957ecfbe44ac6c

Add `ReturnsForAll` and `ThrowsForAll` methods.

jjcansdale committed 8 years ago
Unverified
48741ae447406a5a5e651982fa5fb78f888bee06

Added mocking defaults example.

jjcansdale committed 8 years ago
Unverified
3eb2300b48a0224d372735a58c35bbd65ee17632

Update README.md

jjcansdale committed 8 years ago
Unverified
8a4670862fd8aac5107e98fdd713d52821ef641e

Add check to prevent classes from being mocked by more than one `StaticMock` at a time. #3

jjcansdale committed 8 years ago

README

The README file for this repository.

A mocking library capable of mocking .NET and .NET Core static methods

Getting started

For convenience I've put the sample code here.

  • Take the following target code.
   using System.IO;

   public class TargetClass
   {
       public static string ShoutFile(string path)
       {
           var text = File.ReadAllText(path);
           return text.ToUpperInvariant();
       }
   }
  • Install the StaticMocks package from NuGet (check 'Incude prerelease'). StaticMocks works nicely with NSubstitute 2.0 and will automatically pull in that package. It also has a simple Moq-like interface if you need to remove the NSubstitute dependency.

  • Write the following test code.

    using System;
    using System.IO;
    using StaticMocks;
    using NSubstitute;
    using NUnit.Framework;

    public class TargetClassTests
    {
        [Test]
        public void ShoutFile()
        {
            using (var staticMock = new StaticMock(typeof(TargetClass)))
            {
                staticMock.For(() => File.ReadAllText("foo.txt")).Returns("bar");

                var text = TargetClass.ShoutFile("foo.txt");

                Assert.That(text, Is.EqualTo("BAR"));
            }
        }
    }
  • Run the ShoutFile test. If you're using TestDriven.Net you will see the following.
------ Test started: Assembly: Target.Tests.dll ------

Test 'Target.Tests.TargetClassTests.ShoutFile' failed: StaticMocks.StaticMockException:

Please add the following as a nested class to 'Target.Tests.TargetClass':

class File
{
    internal static Func<string, string> ReadAllText = (string path) => System.IO.File.ReadAllText(path);
}
	Samples\Sample1.cs(17,0): at Samples.Tests.TargetClassTests.ShoutFile()

0 passed, 1 failed, 0 skipped, took 0.67 seconds (NUnit 3.4.1).
  • Do as the exception message suggests and change your target code to this.
    using System;

    public class TargetClass
    {
        public static string ShoutFile(string path)
        {
            var text = File.ReadAllText(path);
            return text.ToUpperInvariant();
        }

        class File
        {
            internal static Func<string, string> ReadAllText = (string path) => System.IO.File.ReadAllText(path);
        }
    }
  • Run the ShoutFile test again and rejoice when the test passes!

How can you verify that a static method was called?

You can do this using StaticMock.Received. For example, the following method:

[Test]
public void ShoutFile()
{
    using (var staticMock = new StaticMock(typeof(TargetClass)))
    {
        staticMock.For(() => File.ReadAllText("foo.txt")).Returns("bar");

        var text = TargetClass.ShoutFile("foo.txt");

        staticMock.Received(1, () => File.ReadAllText("foo.txt"));
    }
}

Will fail with the following:

Test 'ShoutFile' failed: NSubstitute.Exceptions.ReceivedCallsException : Expected to receive exactly 1 call matching:
	Invoke("foo.txt")
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
	Invoke(*"oops.txt"*)

How do I specify default values or exceptions?

For methods that return a simple value, you can specify the default Returns value before the specific values. For example:

staticMock.For(() => File.Exists(null)).ReturnsForAnyArgs(false);
staticMock.For(() => File.Exists("foo.txt")).Returns(true);

Alternatively, you can call ReturnsForAll after specifying your Returns.

staticMock.For(() => File.Exists("foo.txt")).Returns(true);
staticMock.ReturnsForAll(false);

If you need to throw an exception in the default case, you can call ThrowsForAll after specifying your Returns.

staticMock.For(() => File.ReadAllText("foo.txt")).Returns("bar");
staticMock.ThrowsForAll(new FileNotFoundException());

What happens if tests are being run in parallel?

StaticMocks enforces a rule that a target type can only be mocked by one StaticMock at a time. If a parallel test attempts to mock a type more than once, the test will fail with a StaticMockException.

For example, the tests below will fail with the following message:

------ Test started: Assembly: StaticMocks.ParallelTests.dll ------

Test 'ParallelTests.FooTests.Test' failed: StaticMocks.StaticMockException : There is already an active `StaticMock` for type `Reader`.
If you're using xUnit, ensure that test classes that create a `StaticMock` for `Reader` belong to the same collection.
For example, you could add [Collection("ReaderTests")] to these classes.
	StaticMocks.ParallelTests\Class1.cs(15,0): at ParallelTests.FooTests.Test()

1 passed, 1 failed, 0 skipped, took 0.71 seconds (xUnit.net 2.2.0 build 3402).

To fix this error, uncomment the [Collection("ReaderTests")] attributes.

namespace ParallelTests
{
    using System;
    using System.IO;
    using Xunit;
    using NSubstitute;
    using StaticMocks;

    // [Collection("ReaderTests")]
    public class FooTests
    {
        [Fact]
        public void Test()
        {
            using (var staticMock = new StaticMock(typeof(Reader)))
            {
                staticMock.For(() => File.ReadAllText("readme.txt")).Returns("foo");
                Assert.Equal("foo", Reader.ReadAllText("readme.txt"));
            }
        }
    }

    // [Collection("ReaderTests")]
    public class BarTests
    {
        [Fact]
        public void Test()
        {
            using (var staticMock = new StaticMock(typeof(Reader)))
            {
                staticMock.For(() => File.ReadAllText("readme.txt")).Returns("bar");
                Assert.Equal("bar", Reader.ReadAllText("readme.txt"));
            }
        }
    }

    public class Reader
    {
        public static string ReadAllText(string path)
        {
            return File.ReadAllText(path);
        }

        class File
        {
            internal static Func<string, string> ReadAllText = (string path) => System.IO.File.ReadAllText(path);
        }
    }
}

The [Collection(...)] attributes are only necessary when a target class is being mocked by multiple xUnit test fixtures. By default NUnit doesn't do parallel testing of fixtures, so this shouldn't be an issue unless you explicitly enable it.

Conclusion

You've just tested a static method without touching the public interface of its containing class or running your tests with a special tool. This was made possible by adding a single auto-generated field/class to the class under test. You could use #if DEBUG to remove this from the released assembly if you're so inclined.

This is by no means perfect, but it's useful in a lot of common scenarios. I've been dogfooding it for a little while now.

FAQ

Ask away in the issues section or tweet me! jcansdale@twitter.com