1

I have two interfaces like this:

public interface Store { Tx BeginTx() }
public interface Tx { void Write() }

And I have a method like this:

void WriteALot(fakeStore) 
{
    var tx1 = fakeStore.BeginTx();
    tx1.Write();

    var tx2 = fakeStore.BeginTx();
    tx2.Write();
}

And a test:

var fakeStore = A.Fake<Store>(x => x.Wrapping(realstore));

// A.CallTo(() => fakeStore.BeginTx()).ReturnAWrappedTx()?

WriteALot(fakeStore);

// Check that a total of two calls were made to the Tx's

Can this be done?

EDIT:

I should clarify that there will actually be several hundred transactions and multiple Write-calls to each. And the implementations of the Store and Tx are complex. This is for an integration test and I use FakeItEasy for inspection of batching-behavior under different setups. It might a little too far from the intended use case for the library though :)

I guess what I'm asking is if I can collect and preferably merge the faked transactions without doing it manually. I can imagine something like ReturnLazily with a side-effect of collecting the returned fakes, but that is pretty unmanageable and hard to read (and I couldn't get the assertion part working).

asgerhallas
  • 16,890
  • 6
  • 50
  • 68

2 Answers2

1

Assuming you meant to write tx1.Write and tx2.Write above, you can easily check that each transaction was called once, which is probably more useful than checking that a total of two calls was made:

public void Test()
{
    var realStore = new RealStore();
    var fakeStore = A.Fake<Store>(x => x.Wrapping(realStore));

    var realTransaction1 = new RealTransaction();
    var realTransaction2 = new RealTransaction();

    var wrappedTransaction1 = A.Fake<Tx>(options => options.Wrapping(realTransaction1));
    var wrappedTransaction2 = A.Fake<Tx>(options => options.Wrapping(realTransaction2));
    A.CallTo(() => fakeStore.BeginTx())
        .Returns(wrappedTransaction1).Once().Then
        .Returns(wrappedTransaction2);

    WriteALot(fakeStore);

    A.CallTo(() => wrappedTransaction1.Write()).MustHaveHappenedOnceExactly();
    A.CallTo(() => wrappedTransaction2.Write()).MustHaveHappenedOnceExactly();
}

But if you really want to make sure that two calls were made without checking that each transaction was responsible for 1 write, you could

[Test]
public void LaxTest()
{
    int numberOfTransactionCalls = 0;

    var realStore = new RealStore();
    var fakeStore = A.Fake<Store>(x => x.Wrapping(realStore));

    var realTransaction1 = new RealTransaction();
    var realTransaction2 = new RealTransaction();

    var wrappedTransaction1 = A.Fake<Tx>(options => options.Wrapping(realTransaction1));
    var wrappedTransaction2 = A.Fake<Tx>(options => options.Wrapping(realTransaction2));

    A.CallTo(() => wrappedTransaction1.Write()).Invokes(() => ++numberOfTransactionCalls);
    A.CallTo(() => wrappedTransaction2.Write()).Invokes(() => ++numberOfTransactionCalls);

    A.CallTo(() => fakeStore.BeginTx())
        .Returns(wrappedTransaction1).Once().Then
        .Returns(wrappedTransaction2);

    WriteALot(fakeStore);

    Assert.That(numberOfTransactionCalls, Is.EqualTo(2));
}

Note that if your production method really is as simple as you post, there's no need to delegate to an actual implementation and you could omit all the wrapping:

[Test]
public void UnwrappedTest()
{
    var fakeStore = A.Fake<Store>();
    var transaction1 = A.Fake<Tx>();
    var transaction2 = A.Fake<Tx>();
    A.CallTo(() => fakeStore.BeginTx())
        .Returns(transaction1).Once().Then
        .Returns(transaction2);

    WriteALot(fakeStore);

    A.CallTo(() => transaction1.Write()).MustHaveHappenedOnceExactly();
    A.CallTo(() => transaction2.Write()).MustHaveHappenedOnceExactly();
}

In my opinion it's a lot easier to understand what's going on. But maybe you just simplified for the sake of asking the question.

Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
  • Thanks! Yes, I can see now that I over-simplified the setup. I will edit the question to better reflect the actual circumstances. – asgerhallas Oct 09 '18 at 14:26
1

With the updated requirements, I tried this kind of thing, and it passed. I'm sure it's still overly simplistic, but it sounds like your tests will be varied and weird, and I really don't have a chance of writing something that will fit your particular use case. However, by introducing a small factory class, I achieved some level of readability (to my mind), and was able to gather up the created transactions:

private class TransactionFactory
{
    private readonly IList<Tx> allTransactions = new List<Tx>();

    public IEnumerable<Tx> AllTransactions => allTransactions;

    public Tx Create()
    {
        var realTransaction = new RealTransaction();
        var fakeTransaction = A.Fake<Tx>(options =>
                    options.Wrapping(realTransaction));
        allTransactions.Add(fakeTransaction);
        return fakeTransaction;
    }
}


[Test]
public void UpdatedTests()
{
    var realStore = new RealStore();
    var fakeStore = A.Fake<Store>(x => x.Wrapping(realStore));

    var transactionFactory = new TransactionFactory();

    A.CallTo(() => fakeStore.BeginTx()).ReturnsLazily(transactionFactory.Create);

    WriteALot(fakeStore);

    Assert.That(transactionFactory.AllTransactions.SelectMany(Fake.GetCalls).Count(),
                Is.EqualTo(2));
}

This should be amenable to various modifications, but as you point out it's not exactly how FakeItEasy expected to be used, so you're likely going to end up doing a lot of custom coding around the library.

Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
  • 1
    Ah! `Fake.GetCalls()` that was the missing piece of the puzzle. That what was I was trying to get at with my "merged fakes" line of thought. This is a great answer and creating/collecting the faked transaction in a dedicated class lets the test tell a much clearer story :) Thanks! – asgerhallas Oct 11 '18 at 08:06
  • 1
    Ah, yes. You make a good point - `GetCalls` isn't documented. I've created [an issue to address this](https://github.com/FakeItEasy/FakeItEasy/issues/1474). – Blair Conrad Oct 11 '18 at 13:18