Skip to content
I Freeman-Yates edited this page Apr 7, 2022 · 4 revisions

Welcome to the Shimr wiki!

Start with the project README for an overview of the library.
This wiki will eventually cover individual features and detailed examples.

Featureset

  • Shim property/field/method
    • Auto-shim type
  • Shim method (inc. generic-method)
    • Auto-shim method return
    • Auto-unshim method parameter
  • Static members
  • Constructors
  • Events
  • Rename member
  • Support implemented interfaces

Basics

Any public field, property, method, or event can be shimmed to a facade with one line of code:

public class TestClass {
    public string Value { get; set; }
    public void Test() {
        ...
    }
}
public interface ITest {
    string Value { get; set; }
    void Test();
}

ITest asFacade = new TestClass().Shim<ITest>();

If TestClass does not contain all of the items in ITest, this will fail. The converse is not true; just like a normal class-interface relationship.

Renaming Members

The proxy/facade patterns can also make it easier to unify different implementations. Shimr can help by enabling renaming of implementation members using the ShimAttribute.

public class TestClass {
    public string LibrarySpecificName() {
        ...
    }
}
public interface IFacadeClass {
    [Shim("LibrarySpecificName")]
    string CommonName();
}

Fields

Shimr will allow you to define a property in your proxy to cover a field in the implementation:

public class TestClass {
    public string Name;
}
public interface ITest {
    string Name { get; set; }
}
// Use: new TestClass().Shim<ITest>().Name

Note: If the underlying field is readonly, defining the set will not fail on shim, but will fail on use.

Renaming with the ShimAttribute and auto-shimming both work in the way you'd expect.

Overriding return/parameter types

For simple class members, the above will be enough to decouple from the concrete class; however, if the member returns or takes other concrete types, Inversion of Control and unit testability are not hugely improved. The "auto" shim and unshim logic can be used to further decouple from the concrete classes.

public class TestClass {
    TestClass Value { get; set; }
    public OtherTestClass Convert(AnotherTestClass from) {
        ...
    }
}
public interface IClassConverter {
    ITestClass Value { get; set; }
    IOtherTestClass Convert([TypeShim(typeof(AnotherTestClass))] IAnotherTestClass from);
}

If the return type of the interface member is not the true return type of the method being proxied, it must be an interface that the return value will be auto-shimmed to. The TypeShimAttribute on the parameter specifies the true type of the parameter in the method being proxied. The interface type will be unshimmed to the original type when passed in. Note that the passed in object must also implement IShim (all Shimr shims will do this) in order to be returned to the original class parameter type.

These concepts can automatically be applied to arrays and direct IEnumerable<?> usages.

Static members

For situations where you need to proxy static members, Shimr provides a ShimBuilder.Create<>() method and the StaticShimAttribute. This allows for using proxies around static methods at runtime, but creating instance mocks at test. A shim interface cannot contain a mix of static and instance member shims, but can contain static methods proxied from different types.

public class TestClass {
	public static string Value { get; set; }
	public static void Test() {
		...
	}

	// Instance members
}
public class AnotherTestClass {
	public static void AnotherTest() {
		...
	}
}
public interface IStaticTest {
	[StaticShim(typeof(TestClass))]
	string Value { get; set; }
	[StaticShim(typeof(TestClass))]
	void Test();
	[StaticShim(typeof(AnotherTestClass))]
	void AnotherTest();
}

public void DoTest() {
	IStaticTest factory = ShimBuilder.Create<IStaticTest>();
	factory.Test();
}

The attribute can be placed on the interface, to reduce usages:

[StaticShim(typeof(TestClass))]
public interface IStaticTest {
	string Value { get; set; }
	void Test();
	[StaticShim(typeof(AnotherTestClass))]
	void AnotherTest();
}

Constructors

As with static methods, Shimr allows you to call instance constructors from a static factory based on arguments, where the name of the factory method is unchecked and the return type is assumed to be the constructor provider or a shim of it. Since constructors are treated like static members, they can be mixed in the same interface.

public class TestClass {
	public TestClass(string arg1) {
		...
	}

	// Instance members
}
public interface ITestFactory {
	[ConstructorShim(typeof(TestClass))]
	ITestClass CreateNew(string arg1);
}
public interface ITestClass {
	// Instance members
}

public void DoTest() {
	ITestFactory factory = ShimBuilder.Create<ITestFactory>();
	ITestClass inst = factory.CreateNew();
}

If the StaticShimAttribute has been placed on the interface for the same type, the declaration can be simplified:

[StaticShim(typeof(TestClass))]
public interface ITestFactory {
	[ConstructorShim]
	ITestClass CreateNew(string arg1);
}

Implemented interface members

As Shimr shims are normal interfaces, implemented members work as normal.

public interface ITestShim {
	void Test(); // Shim
  public void Test2() {
    // Logic
  }
}

Note that this will refer to the shim instance, so calling the same member will cause recursion.

Limitations

  • Currently, generic methods will not provide any auto-shim logic on the generic constraints.
  • Returns and parameters containing deep generic types (e.g., Func<TestClass>) will not be auto-shimmed; except for IEnumerable<>