Post

LocalizationProvider Under the Tests

Recently I got a question from a very good friend of mine about how to properly unit test localization provider.

Tests and even unit ones have been one of the areas where we probably all have committed sins. And I’m no exception. Only recently I’ve added an interface in front of LocalizationProvider class to make it easier to work in unit tests.

Adding an interface is just part of the solution (and the easiest one). Writing unit test for the localization provider turned out not to be so easy.

Unit Test with FakeItEasy

Challenge to write proper unit tests for the localization provider is due to fact that library heavily relies on expressions (aka lambdas).

First Attempt

I would write this naive unit tests in hope that it will run green:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ResourceClass
{
    public static string SomeProperty { get; } = "Some value of the property";
}

public class SomeServiceWithLocalization
{
    private readonly ILocalizationProvider _provider;

    public SomeServiceWithLocalization(ILocalizationProvider provider)
    {
        _provider = provider;
    }

    public string GetTranslation()
    {
        return _provider.GetString(() => ResourceClass.SomeProperty);
    }
}

[Fact]
public void TestInterfaceMock()
{
    var fake = A.Fake<ILocalizationProvider>();

    A.CallTo(() => fake.GetString(() => ResourceClass.SomeProperty)).Returns("Fake");

    var sut = new SomeServiceWithLocalization(fake);

    var result = sut.GetTranslation();

    Assert.Equal("Fake", result);
}

But if you run this - it will be red.

This is because FakeItEasy (and other mocking frameworks) uses argument comparer to understand whether specified arguments are the same that has been used to configure mock - and if so, specified behavior (in this case return value) should be executed.

Turns out Expression is not something that is easy to compare out of the box. And expression you specified while setting up mock is not the same as one used in SomeServiceWithLocalization.

You can even compare them directly in unit test:

1
2
3
4
5
6
7
8
[Fact]
public void CompareExpressions()
{
    Expression<Func<object>> exp = () => ResourceClass.SomeProperty;
    Expression<Func<object>> exp2 = () => ResourceClass.SomeProperty;

    Assert.Equal(exp, exp2);
}

failed-unittest

This still fails.

Add Expression Comparer

What you need to do instead is to teach FakeItEasy how to do an expression comparison. For this task you can use some ready made packages (like Neleus.LambdaCompare).

Or you can search around internets and find maybe something suitable for you. I found quite comprehensive expression comparer from Joseph. This does its job quite well.

Make sure that you copy this somewhere in your unit test project (or shared project that you can use across all your unit tests).

Second Attempt

With having this comparer in our toolkit we can write second attempt to test localization provider interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Fact]
public void TestInterfaceMock()
{
    var fake = A.Fake<ILocalizationProvider>();
    Expression<Func<object>> expression = () => ResourceClass.SomeProperty;
    var comparer = new ExpressionComparer();

    A
        .CallTo(() => fake.GetString(A<Expression<Func<object>>>.That.Matches(e => comparer.Equals(expression, e))))
        .Returns("[SomeProperty] Value from fake");

    var sut = new SomeServiceWithLocalization(fake);

    var result = sut.GetTranslation();

    Assert.Equal("[SomeProperty] Value from fake", result);
}

Now this test is green.

We can even verify with second expression just to be sure that mock is working as expected.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class ResourceClass
{
    public static string SomeProperty { get; } = "Some value of the property";
    public static string SomeProperty2 { get; } = "Another translation";
}

public class SomeServiceWithLocalization
{
    private readonly ILocalizationProvider _provider;

    public SomeServiceWithLocalization(ILocalizationProvider provider)
    {
        _provider = provider;
    }

    public string GetTranslation()
    {
        return _provider.GetString(() => ResourceClass.SomeProperty);
    }

    public string GetTranslation2()
    {
        return _provider.GetString(() => ResourceClass.SomeProperty2);
    }
}

[Fact]
public void TestInterfaceMock()
{
    var fake = A.Fake<ILocalizationProvider>();
    Expression<Func<object>> expression = () => ResourceClass.SomeProperty;
    Expression<Func<object>> expression2 = () => ResourceClass.SomeProperty2;
    var comparer = new ExpressionComparer();

    A
        .CallTo(() => fake.GetString(A<Expression<Func<object>>>.That.Matches(e => comparer.Equals(expression, e))))
        .Returns("[SomeProperty] Value from fake");

    A
        .CallTo(() => fake.GetString(A<Expression<Func<object>>>.That.Matches(e => comparer.Equals(expression2, e))))
        .Returns("[SomeProperty] Value from fake 2");

    var sut = new SomeServiceWithLocalization(fake);

    var result = sut.GetTranslation();
    var result2 = sut.GetTranslation2();

    Assert.Equal("[SomeProperty] Value from fake", result);
    Assert.Equal("[SomeProperty] Value from fake 2", result2);
}

Happy unit testing your translations!

[eof]

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.