Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Consider our domain logic is some kind of game with character with health and so on.
- type Player {
- Id: int
- Name: string
- Health: int
- }
- // Because we have a lot of those characters we have some kind of service that manages all of those characters
- // this service is able to find or search for any character. Now lets assume we want to create a function
- // that damage or heal a character, we could do it like this:
- let heal service id amount =
- let character = service.fetch(id)
- {character with Health = (character.Health + amount)} // This returns a new character with Health changed
- let damage service id amount =
- let character = service.fetch(id)
- {character with Health = (character.Health - amount)} // This returns a new character with Health changed
- // "service" is an object that is able to fetch the character by its id. The whole idea is actually Dependency Injection.
- // We can switch out "service" with something different. But if we want to test "heal" or "damage" we still must provide
- // a service. Let's consider the default "service" is too hard to create and for some reason to wired with other systems.
- // What we want is another "service" that we just can provide some in-memory data that it returns. In the case of an object
- // we still need an interface and create a whole new class. But this isn't the only problem.
- //
- // We also need to change our code that somewhere that different class for fetching is used. One way would be mocking. So
- // whenever there is an instance of the original Service created now your new fake class is used. If you don't have mocking
- // avaiable then replacing it becomes even harder and a mess. One way would be to replace the "service" object by a
- // function
- let heal fetchCharacter id amount =
- let character = fetchCharacter id
- {character with Health = (character.Health + amount)} // This returns a new character with Health changed
- let damage fetchCharacter id amount =
- let character = fetchCharacter id
- {character with Health = (character.Health - amount)} // This returns a new character with Health changed
- // Doesn't seems like a big difference, but now that we have a function that returns a character it is way more easier
- // to use that function with testing. We don't need to create a whole service class anymore, also no interfaces and so on
- // is needed. For testing we could directly create an inline function that just returns a character
- let updatedChar = heal (fun _ -> {Id=10; Name="Foo"; Health=100}) 10 100
- Assert.Equal(updatedChar.Health, 200)
- // Expecting functions instead of service classes means we can very easily replace the service class, no mocking is needed.
- // In general Testing becomes a lot easier.
- //
- // But another solution would be to remove the side-effect of fetching something completely from the "heal" or "damage" function
- // Instead of fetching and healing or fetching and damage, they just expect a character and just do they thing with it.
- //
- // The general rule: Don't pass things that get things, pass things directly.
- //
- // So the updated "heal" and "damage" functions looks like this:
- let heal character amount =
- {character with Health = (character.Health + amount)} // This returns a new character with Health changed
- let damage character amount =
- {character with Health = (character.Health - amount)} // This returns a new character with Health changed
- // With those updates you also can test easily if your core logic works like intended
- let updatedChar = heal {Id=10; Name="Foo"; Health=100} 100
- Assert.Equal(updatedChar, 200)
- // There is only one thing now missing. Most of your application don't work by passing characters around. Your
- // application works by passing "id" around. So what you need is only one function with a side-effect. You
- // need a function that is able to fetch a character from your service. Or technically you can even
- // keep the previous "service" class if you want. So how does your code change?
- // Previously you had a line of code like this:
- heal service id 100
- // In the above you got your service from somewhere. You also got your "id" from somewhere and you pass all things
- // to the heal function, this function had fetched the correct character and increased it health, and because of
- // this was harder to test.
- //
- // With the updated idea that "heal" and "damage" don't fetch the character anymore, now at this place you must first
- // fetch the character, and then heal it. Now your code changes to this:
- let character = service.fetch(id)
- heal character 100
- // You still have your "service" and just your "id". With those both you fetch your character. This character then can
- // be used with the "heal" function. But the advantage is. "heal" doesn't fetch anything from anywhere and is easily testable.
- // You also never need to change "service" or mock "service". Consider, the only reason why you ever wanted to mock "service"
- // was that you could test your "heal" or "damage" function. But the new functions don't depend on a "service" class.
- //
- // How does that change any Observable code, other kind of stuff? It don't change it all. You still can have Observables that
- // pass "id" around, and those "id" are async and so on. But instead of passing a "service" or a "fetchFunc" to another function
- // you just pass the result to it. In general you can say. If you have something like:
- let func (fetch: id -> Thing) a b c =
- ...
- // you should replace it with
- let func Thing a b c =
- ...
- // This way "func" becomes completely independent, free of any side-effects, and is easily testable.
Advertisement
Add Comment
Please, Sign In to add comment