Before:
  runtime autoload/ale/linter.vim
  runtime autoload/ale/lsp.vim
  runtime autoload/ale/lsp_linter.vim

  let g:address = 'ccls_address'
  let g:conn_id = -1
  let g:executable = 'ccls'
  let g:executable_or_address = ''
  let g:linter_name = 'ccls'
  let g:magic_number = 42
  let g:no_result = 0
  let g:message_list = []
  let g:message_id = 1
  let g:method = '$ccls/call'
  let g:parameters = {}
  let g:project_root = '/project/root'
  let g:response = ''
  let g:return_value = -1

  let g:linter_list = [{
  \   'output_stream': 'stdout',
  \   'lint_file': 0,
  \   'language': 'cpp',
  \   'name': g:linter_name,
  \   'project_root': {b -> g:project_root},
  \   'aliases': [],
  \   'read_buffer': 1,
  \   'command': '%e'
  \ }]

  let g:callback_result = g:no_result

  " Encode dictionary to jsonrpc
  function! Encode(obj) abort
    let l:body = json_encode(a:obj)
    return 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
  endfunction

  " Replace the StartLSP function to mock an LSP linter
  function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
    let g:conn_id = ale#lsp#Register(g:executable_or_address, g:project_root, '', {})
    call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
    call ale#lsp#HandleMessage(g:conn_id, Encode({'method': 'initialize'}))

    let l:details = {
    \ 'command': g:executable,
    \ 'buffer': a:buffer,
    \ 'connection_id': g:conn_id,
    \ 'project_root': g:project_root,
    \}

    call ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)
  endfunction

  " Dummy callback
  function! Callback(response) abort
    let g:callback_result = a:response.result.value
  endfunction

  " Replace the GetAll function to mock an LSP linter
  function! ale#linter#GetAll(filetype) abort
    return g:linter_list
  endfunction

  " Replace the Send function to mock an LSP linter
  function! ale#lsp#Send(conn_id, message) abort
    call add(g:message_list, a:message)
    return g:message_id
  endfunction

  " Code for a test case
  function! TestCase(is_notification) abort
    " Test sending a custom request
    let g:return_value = ale#lsp_linter#SendRequest(
    \  bufnr('%'),
    \  g:linter_name,
    \  [a:is_notification, g:method, g:parameters],
    \  function('Callback'))

    Assert index(g:message_list, [a:is_notification, g:method, g:parameters]) >= 0

    " Mock an incoming response to the request
    let g:response = Encode({
    \  'id': g:message_id,
    \  'jsonrpc': '2.0',
    \  'result': {'value': g:magic_number}
    \ })
    call ale#lsp#HandleMessage(g:conn_id, g:response)

    AssertEqual
    \ a:is_notification ? g:no_result : g:magic_number,
    \ g:callback_result
  endfunction

After:
  if g:conn_id isnot v:null
    call ale#lsp#RemoveConnectionWithID(g:conn_id)
  endif

  unlet! g:callback_result
  unlet! g:conn_id
  unlet! g:executable
  unlet! g:is_notification
  unlet! g:linter_name
  unlet! g:magic_number
  unlet! g:message_list
  unlet! g:message_id
  unlet! g:method
  unlet! g:no_result
  unlet! g:parameters
  unlet! g:project_root
  unlet! g:response
  unlet! g:return_value

  delfunction Encode
  delfunction Callback
  delfunction TestCase

  runtime autoload/ale/linter.vim
  runtime autoload/ale/lsp.vim
  runtime autoload/ale/lsp_linter.vim

Given cpp(Empty cpp file):
Execute(Test custom request to server identified by executable):
  let g:executable_or_address = g:executable
  let g:linter_list[0].executable = {b -> g:executable}
  let g:linter_list[0].lsp = 'stdio'
  let g:is_notification = 0

  call TestCase(g:is_notification)

Given cpp(Empty cpp file):
Execute(Test custom notification to server identified by executable):
  let g:executable_or_address = g:executable
  let g:linter_list[0].executable = {b -> g:executable}
  let g:linter_list[0].lsp = 'stdio'
  let g:is_notification = 1

  call TestCase(g:is_notification)

Given cpp(Empty cpp file):
Execute(Test custom request to server identified by address):
  let g:executable_or_address = g:address
  let g:linter_list[0].address = {b -> g:address}
  let g:linter_list[0].lsp = 'socket'
  let g:is_notification = 0

  call TestCase(g:is_notification)

Given cpp(Empty cpp file):
Execute(Test custom notification to server identified by address):
  let g:executable_or_address = g:address
  let g:linter_list[0].address = {b -> g:address}
  let g:linter_list[0].lsp = 'socket'
  let g:is_notification = 1

  call TestCase(g:is_notification)
