I'm looking to do some basic verification testing on my functions that write to the filesystem.
I took a hint from here on how to mock out the filesystem using an interface, but I'm kinda bummed on how the FileSystemOperations
type needs to be passed along through every function. Any ideas how to improve this, or where I went wrong?
I'm rather new to FP, so any feedback on other design issues are welcome as well.
module FileSystem =
open System.IO
type IFileSystemOperations =
abstract member copy: string -> string -> bool -> unit
abstract member delete: string -> unit
abstract member readAllBytes: string -> byte[]
abstract member createDirectory: string -> DirectoryInfo
abstract member directoryExists: string -> bool
type FileSystemOperations () =
interface IFileSystemOperations with
member this.copy source destination overwrite =
File.Copy(source, destination, overwrite)
member this.delete path =
File.Delete path
member this.readAllBytes path =
File.ReadAllBytes path
member this.createDirectory path =
Directory.CreateDirectory path
member this.directoryExists path =
let fileInfo = FileInfo path
fileInfo.Directory.Exists
module FileMover =
open FileSystem
let private ensureDirectoryExists (fileSystemsOperations:IFileSystemOperations) destination =
let directoryExists = fileSystemsOperations.directoryExists destination
if not (directoryExists) then
fileSystemsOperations.createDirectory destination |> ignore
let private compareFiles (fileSystemsOperations:IFileSystemOperations) moveRequest =
let sourceStream = fileSystemsOperations.readAllBytes moveRequest.Source
let destinationStream = fileSystemsOperations.readAllBytes moveRequest.Destination
sourceStream = destinationStream
let private copyToDestination (fileSystemsOperations:IFileSystemOperations) (moveRequest:MoveRequest) =
ensureDirectoryExists fileSystemsOperations moveRequest.Destination
fileSystemsOperations.copy moveRequest.Source moveRequest.Destination true
let private deleteSource (fileSystemsOperations:IFileSystemOperations) (move:Move) =
fileSystemsOperations.delete move.Request.Source
let private moveFile (fileSystemsOperations:IFileSystemOperations) moveRequest =
copyToDestination fileSystemsOperations moveRequest
let filesMatch = compareFiles fileSystemsOperations moveRequest
let move = match filesMatch with
| true -> {Request = moveRequest; Result = Success}
| false -> {Request = moveRequest; Result = Failure BytesDidNotMatch}
deleteSource fileSystemsOperations move
let move (fileSystemsOperations:IFileSystemOperations) targetPath pictures =
let moveFile = moveFile fileSystemsOperations
pictures
|> getMoveRequests targetPath
|> Seq.map moveFile
Update (for reference & anyone interested)
Based on the answers below, here is how I currently refactored the code. It's a work in progress, but it's already more easily testable (main goal) and the responsibilities seem to fit better as well (eg: injecting ensureDirectoryExists
into copyToDestination
instead of into moveFile
).
Thanks for your answers!