1

In short, "functional core, imperative shell" can be summarized as:

  • functional core implements logic; you unit test it. Your tests call real functions just like in production real functions are called; ie compared to mocking / faking, you don't run a risk that mocks / fakes mildly mismatch behavior of the real dependencies

  • imperative shell basically just handles dependencies. It extracts values from them / returns results to them, but has no logic -- everything is delegated by calling functional core. You don't unit test it, because there is no logic to test

So translating this to java (for pseudo-code purposes only, my question isn't java specific), my classes have been looking something like

public class Foo {

  ...

  public int doBar() {
    return doBarImpl(fetchValueFromDB());
  }

  static int doBarImpl(int value) {
    return value + 1;
  }
}

The referentialy transparent / functional core methods are package private and inaccessibly from public interface, but I can still test them by declaring my unit test classes to have same package. This does run against the whole "don't test your private methods" idea. Declaring them public, either in same class or new one, would misrepresent it, because it is ultimately an implementation detail, and I don't want other classes to rely on its existence (naturally this is when functionality isn't generic enough; if it's generic and reusable, then there is no issues / questions to make it public). Keeping it private but testing through public methods throws away the whole benefit I initially mentioned, in that you're not testing method calls directly anymore but start using mocks and fakes and again step into the risk of them not matching the real thing in production. Is my understanding correct, that it is explicitly expected to test private methods in this "functional core, imperative shell" design and ignore the rule of thumb about not testing private methods

1
  • @jonrsharpe I was told it's better fit here (though looks like that comment has since been deleted) Commented Aug 28, 2021 at 11:48

2 Answers 2

6

I don't see a compelling reason why the splitting between "functional core" and "imperative shell" must happen inside a single class. Since this is known to be an architectural pattern (and not a class design pattern), I would expect these two terms to refer to layers in a system, placed in separate packages, like this

// inside "shell" package
public class FooShell {
  public int doBar() {
    return Foo.doBar(fetchValueFromDB());
  }
}

// ...
// inside "core" package
public class Foo
{
  public static int doBar(int value) {
    return value + 1;
  }
}

But you wrote

Declaring them public, either in same class or new one, would misrepresent it, because it is ultimately an implementation detail, and I don't want other classes to rely on its existence (naturally this is when functionality isn't generic enough)

Sorry, but I think that's a way-too-dogmatic point of view, hence I strongly disagree. If functional core functions are used from the shell package, then they are already "generic enough" to be used and reused from somewhere else, especially from the tests. Just throw your dogma over board - problem solved.

1

No. You example is temping because it implies that doBar is AddOne() and thus you want to test the adding one to number functionality. But this is a misrepresentation of the general pattern.

You are more likely to have

public class maths
{
   private IRepo database
   public int AddOneToStoredValue()
   {
      var x= database.getValue()
      x = this.addTwo(x)
      x = this.minusOne(x)
      database.UpdateValue(x)
      return x
   }
}

Now do you want to test addTwo and minusOne? I would contend not. You only care that the current db value + 1 is returned and that the db is updated, not the implementation.

You can do both a unit test, where the IRepo is mocked, and an integration test where an actual db is used. These will both have value and won't break when you change the implementation to addOne(x) or some other method.

8
  • "where the IRepo is mocked" but that's the point, I don't want mocking, I want to test against "the real boundaries" as per idea of whole "fp core, imperative shell". Yes, integration test with a real db is good to have, but my question is specifically about unit testing. I gave a rather silly example to show how dependencies are passed into logic methods, but in real code functional methods aren't trivial or short one liners, and purely functional methods constitute majority of loc (since that's where all logic is at) Commented Aug 28, 2021 at 11:23
  • 1
    I understand what you are saying, you have functions which encapsulate a bit of complex logic and you want to test that logic. But calling the "imperative shell" or interface will also call those methods and testing that interface will also test the methods. Theres nothing about "imperative shell/functional internal" which makes it any more necessary or desirable to test private methods/functions
    – Ewan
    Commented Aug 28, 2021 at 12:06
  • Calling through imperative shell necessarily relies on mocking. And if mocks don't line up with real dependencies' behavior, then you might get passing tests and failing production. Testing private methods directly in this case removes this risk; since they're referentialy transparent, it means there is no runtime context difference of method's callsite in test and method's callsite in production Commented Aug 28, 2021 at 12:21
  • you just change the risk to be "the public method doesnt call the right private method/int the right order/in the right way
    – Ewan
    Commented Aug 28, 2021 at 14:48
  • 1
    If you want to test the private method, im not saying its the worst idea, just that this pattern doesn't imply that you should do it any more than 'normal' OOP with private methods
    – Ewan
    Commented Aug 29, 2021 at 10:59

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