1

I am currently trying to test a file manager class. This class is responsible for keeping track of how long the file is good for. The file will have a date written into it that denotes when it was generated as well as the duration of how long the file is good for. Consumers of my interface will be agnostic to these details. The interface looks like:

public interface IFileManager
{
    bool GenerateFile(string location);
    bool DeleteFile(string location);
    bool IsFileValid(string location);
    bool Update(string customerID, string location);
}

I am concerned with how to test one of these methods, specifically IsValid(string location). Part of the implementation I am planning on for IsValid will be to check whether the file has expired, as well as the file not just containing garbage.

Okay, so here is my conundrum. I always write my unit tests in a separate project, where they only have a reference to an interface library. That is, I test everything through an interface so my unit tests will only know about IFileManager. So from a high-level interface, how do I test a specific implementation of an "expired" or "garbage" file?

My interface does not allow for a client of said interface to create a file that's already expired. And that makes sense to me! On top of that, I don't want to add in functionality to my interface just to be able to test! My thoughts about how can I test this, give me a code smell.

  • 1
    The same way you unit test anything, with Mocks! – esoterik Jun 19 '18 at 19:01
  • @esoterik Would you test an embedded system like that? – Snoop Jun 19 '18 at 19:02
  • Mocks or simulation for unit-tests certainly; (n.b. not the only kind of tests) – esoterik Jun 19 '18 at 19:03
  • 1
    @Snoop I’ve created thin abstractions around Linux syscalls so they could be mocked. I’ve created mock registers for mcu drivers. So... yes. Yes I do test embedded systems that way. – RubberDuck Jun 21 '18 at 1:44
  • 1
    @RubberDuck Awesome, have to check that out at home... and, it kind of sounds like we do similar work. – Snoop Jun 21 '18 at 12:55
5

My thoughts about how can I test this, give me a code smell.

This API is encapsulating a side effect. And that's fine -- most of your code doesn't need to know the details. But for unit testing, you probably need a way to replace the side effect with a test double.

You're implementation is probably using a small number of calls to some File library to do the actual work. So the idea is to have an interface that wraps those calls, and pass an instance of that interface in to get the IFileManager you want.

interface FilePrimitives { ... }

IFileManager fileManager(FilePrimitives filePrimitives) { ... }

The is basically the "Strategy" pattern at work.

In your production code, you initialize the FileManager using an implementation of FilePrimitives that makes actual library calls. The primitives are effectively part of the "imperative shell", where your code interacts with the outside world.

Key point: one of your constraints in implementing the primitives is that they should be too simple to break.

When you are testing, instead of using the "real" primitives, you use a test double, and control the responses from the test.

So if you want to test a garbage file, you set up your test double so that it reacts as if there really is a garbage file, and then use that to initialize the rest of your system.

Your test is really two pieces -- it is both a use case (exercising the API) validator, but it is also a composition root.

So you are saying to abstract the file library and then inject a mock version of it that always reports that a file is expired?

Yes, although I would spell it somewhat differently: we're taking the decision to use this file library, and placing a module boundary around that decision, so that we can change that decision in the test harness.

  • This is a good idea. Could I go so far as to letting the test library use the FilePrimitives interface, without breaking any conventions? – Snoop Jun 19 '18 at 15:06
  • I'm not sure what you mean by "use"; the motivation is to allow you to replace one strategy with another -- meaning that could be a strategy that you implement in your test library. – VoiceOfUnreason Jun 19 '18 at 15:08
  • Okay, I guess I need to re-read this. – Snoop Jun 19 '18 at 15:10
  • So you are saying to abstract the file library and then inject a mock version of it that always reports that a file is expired? – Snoop Jun 19 '18 at 15:14
  • This is a spot on answer. The key is that actually reading/writing the files is a separate responsibility to determining is the content is valid, if its expired etc. So separate the concerns and inject the file read/writer into the FileManager. That way, you can easily test the latter with a mock of the former, allowing your tests to control expiration, content validity etc. – David Arno Jun 19 '18 at 16:06
4

Instead of calling some static system method to get the current time, inject the time source as a dependency. Then you mock the time source to simulate time passing. Here's some Java code:

class FileManager {
    private Supplier<Instant> timeSource;

    public FileManager() {
        this(() -> Instant.now());
    }

    public FileManager(Supplier<Instant> timeSource) { 
         this.timeSource = timeSource;
    }

    public GenerateFile() {
        Instant currentTime = timeSource.get();
        ...
    }
    public IsFileValid() {
      Instant currentTime = timeSource.get();
      ...
    }
}

Now use your favorite mocking library to create a mock time source:

public class FileManagerTest() {
     public void WHEN_file_expired_THEN_not_valid() {
          when(mockTimeSource.get()).thenReturn(someTime)
                                   .thenReturn(aLaterTime);
          FileManager manager = new FileManager(mockTimeSource);
          GenerateFile(...);
          assert(! IsFileValid(...));
     }
}
  • If I understand this correctly... is it similar to what @VoiceOfUnreason is saying? – Snoop Jun 19 '18 at 16:53
  • @Snoop: Maybe, I'm not really sure. I code for testability and refactor as needed. – kevin cline Jun 20 '18 at 1:41
3

If you're testing the implementation of the interface, then you'll have a concrete class, implementing the interface, and the unit tests will be able to access this class.

If you're testing the classes which have IFileManager as a dependency, then create a stub/mock which implements IFileManager and, when calling IsFileValid returns always true or always false or a value which will be configured in every test.

  • Okay, but I care about the process of... is the file expired. How would I be testing that? – Snoop Jun 19 '18 at 15:17
  • It depends entirely on the concrete implementation. I suppose that there is a specific algorithm which determines whether the file is expired. By studying the algorithm, you may find what should be tested and how. – Arseni Mourzenko Jun 19 '18 at 15:24
0

I always write my unit tests in a separate project, where they only have a reference to an interface library. That is, I test everything through an interface so my unit tests will only know about IFileManager

I am repeating at least one answer, but I'd like to additionally emphisize, that this quoted approach does not seem right to me. You can use abstracting a dependency when unit-testing the depended code, thst is: you could inject mock implememtation of IFileManager to test a code which uses FileManager. But to test the FileManager itself you test the concrete class. Limiting it to interface is not yet bad as is, it just does not bring any benefit. And when test needs to use some private details, like to mock the time function, it starts being an obstacle.

  • Hmm... nobody else has really said anything about that so far. – Snoop Jun 20 '18 at 11:23
0

I'll assume that you have knowledge of the implementing class that you are trying to test or at least all the information on how it is implemented.

In your test class you need only instantiate the implementing class:

IFileManager myInstance = new implementingClass();
// prepare the file to test
...
bool result = myInstance.IsFileValid(@"SomeLocation");
// process result
...

If you wanted to get really generic, you can use reflection to get the class (or classes) that implement the interface and use Invoke to call the class' constructor.

// Loop through the .DLLs where the implementation is kept.
// Test that it contains the IFileManager interface
...
ConstructorInfo ci = myModule.GetType().GetInterface(@"IFileManager").GetConstructorInfo(new Type[0]);
// The above needs error handling and validation, an exercise to do.
// Instantiate the class that implemented IFileManager
IFileManager ifm = ci.Invoke(new Object[0]);
// Now perform you tests by calling the methods:
ifm.IsFileValid(@"SomeFile");
...

That is a gross simplification of the steps but the key idea is there. Use reflection to get all the DLLs that implement your interface then test them by instantiating each class and testing the implementation.

  • Are you suggesting that they perform a round trip-test with canned/generated/etc. data? – esoterik Jun 19 '18 at 19:19
  • Yes, that is the usual, however modifying an existing file would work as well. Usually I have test data I create/use that will test all the conditions: Good; Fail and Edge. In this way any new implementation or update can be regression tested to demonstrate it works/doesn't break old code. – CJ1 Jun 20 '18 at 19:40
0

I would say that on the interface level, a unit test of IsFileValid probably doesn't make sense.

Is the fact that file validity is determined by the current time compared to a time range in the file a defined part of the interface? Is it impossible to have different implementations of IFileManager that determine validity by some other metric, such as version number?

If so, then fine, you can test for that (although at the point that implementation details are defined to that extent you're losing some of the point of having an interface). If not, then what exactly would a unit test of IsFileValid be testing? That it returns either true or false? Because either one could be a valid output for a given file in some possible implementation.

If for a specific concrete implementation, file validity is by definition based on a time range as stated above, then unit test that concrete implementation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.