Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,37 @@
/node_modules

#Ignore thumbnails created by Windows
Thumbs.db
#Ignore files built by Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
.vs/
#Nuget packages folder
packages/


*.config
SlackBotPrototype/SlackBotPrototype/App.config
32 changes: 32 additions & 0 deletions SlackBotPrototype/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

#Ignore thumbnails created by Windows
Thumbs.db
#Ignore files built by Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
.vs/
#Nuget packages folder
packages/
72 changes: 72 additions & 0 deletions SlackBotPrototype/API.Tests/API.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BEAB33E8-9F1E-4D39-B741-2069E95834DD}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>API.Tests</RootNamespace>
<AssemblyName>API.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=4.1.1.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.1.1\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.7.99.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.7.99\lib\net45\Moq.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=3.7.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CommandTests.cs" />
<Compile Include="DarkSkyTests.cs" />
<Compile Include="EmbeddedResourceTestBase.cs" />
<Compile Include="LocationServiceTests.cs" />
<Compile Include="MessageParserTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
<EmbeddedResource Include="TestData\dc-daily.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\API\API.csproj">
<Project>{A234D7B8-EAB9-497F-84E7-354DC066BCB5}</Project>
<Name>API</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
122 changes: 122 additions & 0 deletions SlackBotPrototype/API.Tests/CommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;

namespace API.Tests
{

[TestFixture]
public class CommandTests : EmbeddedResourceTestBase
{
private static readonly Location TestLocation =
new Location {Latitude = 38.889931, Longitude = -77.009003, DisplayName = "Washington, DC"};

private IWeatherProvider _weatherProvider;
private IRepository _repository;

[OneTimeSetUp]
public void Init()
{
var mockHttp = new Mock<IHttpRequestClient>();

var dailyForecastJson = GetFromResource("dc-daily.json");
mockHttp.Setup(m => m.GetContent(It.IsAny<string>())).Returns(dailyForecastJson);
_weatherProvider = new DarkSkyWeatherProvider(ConfigConstants.DarkSkyApiSecret, mockHttp.Object);

var mockRepo = new Mock<IRepository>();
mockRepo.Setup(m => m.GetLocation("San Francisco", "CA"))
.Returns(new Location() { City = "San Francisco", State = "CA", DisplayName = "San Francisco, CA" });

_repository = mockRepo.Object;
}

[Test]
public void TestLocationInvalidWeatherCommand()
{

var location = new Location()
{
Latitude = 0,
Longitude = 0
};

var when = DateTime.Parse("2017/07/01");

var weatherCommand = new WeatherNowCommand(_weatherProvider, location, when);
var msg = weatherCommand.Invoke();

// SHORTCUT: A bit of shortcut here, normally we'd define a
// some sort of reponse code enum and test based on the response code.
// Since we don't return a result code right now (requires more boilerplate), just test based on expected string.
Assert.IsTrue(msg.Contains("No location specified"));
}

[Test]
public void TestValidLocationWeatherCommand()
{
var when = DateTime.Parse("2017/07/01");

var weatherCommand = new WeatherNowCommand(_weatherProvider, TestLocation, when);
var msg = weatherCommand.Invoke();

// we know that we always include the city in weather results, this would have
// to be updated as things change
Assert.IsTrue(msg.Contains("Washington, DC"));
}

[Test]
public void TestSetLocationCommand()
{
var location = new Location();

var setLocationCommand = new SetUserLocationCommand(_repository, "Test", "123", location);
var msg = setLocationCommand.Invoke();

Assert.IsTrue(msg.Contains("Location invalid"));
}

[Test]
public void TestSetValidLocationCommand()
{
var setLocationCommand = new SetUserLocationCommand(_repository, "Test", "123", TestLocation);

// Don't think there's much of a point to testing the repository inserts since
// without setting up an integration test with SQLite
var msg = setLocationCommand.Invoke();

Assert.IsTrue(msg.Contains("Set location"));
}

[Test]
public void TestSetNullLocation()
{
var setLocationCommand = new SetUserLocationCommand(_repository, "Test", "123", null);

// Don't think there's much of a point to testing the repository inserts since
// without setting up an integration test with SQLite
var msg = setLocationCommand.Invoke();

Assert.IsTrue(msg.Contains("Location not found"));
}

[TestCase(ExpectedResult = "")]
public string TestNoopCommand()
{
var noop = new NoOpCommand();
return noop.Invoke();
}

[Test]
public void TestWhatToWearCommand()
{
var whatToWear = new WhatToWearCommand(_weatherProvider);
var msg = whatToWear.Invoke();

Assert.IsTrue(msg == "Bring a jacket");
}
}
}
54 changes: 54 additions & 0 deletions SlackBotPrototype/API.Tests/DarkSkyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;

namespace API.Tests
{

/// <summary>
/// Not a unit test, normally would do this in separate REPL but
/// put here for demonstrative purposes
/// </summary>
[TestFixture]
public class TestDarkskyRequests : EmbeddedResourceTestBase
{
private Location _testLocation;
private IWeatherProvider _weatherProvider;

[OneTimeSetUp]
public void Init()
{
_testLocation = new Location() { Latitude = 38.889931, Longitude = -77.0369 };

var mockHttp = new Mock<IHttpRequestClient>();

var dcDailyForecastJson = GetFromResource("dc-daily.json");
mockHttp.Setup(m => m.GetContent(It.IsRegex("-77.0369"))).Returns(dcDailyForecastJson);
_weatherProvider = new DarkSkyWeatherProvider(ConfigConstants.DarkSkyApiSecret, mockHttp.Object);
}

[Test]
public void TestGetDailyForecast()
{
var forecast = _weatherProvider.GetForecast(_testLocation, DateTime.Now);
Assert.IsNotNull(forecast);
}

[Test]
public void TestSummary()
{
var forecast = _weatherProvider.GetForecast(_testLocation, DateTime.Now);
Assert.IsTrue(forecast == "Partly cloudy starting in the evening.");
}

// SHORTCUT: Skip testing for time based weather requests, we'd need to mock out
// the requests which doesn't really help testing the temporal component of the weather
// requests as those are really mainly handled by DarkSky
}
}
36 changes: 36 additions & 0 deletions SlackBotPrototype/API.Tests/EmbeddedResourceTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;

namespace API.Tests
{
public class EmbeddedResourceTestBase

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this doesn't use any instance variables or configure any other aspect of the class, should we look at just having it be either a mixin or a utility function? what are the downsides or upsides of using a class to do this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, both make sense in this case for the reason you specified as alternatives for code sharing. Some considerations

  • In C# we can only inherit one class, so we're preventing ourselves from inheriting another class -- which might have state.
  • By inheriting we're making an assuming that all future additions will be relevant for all inheritors. If we later added instance variables or methods that don't apply, we end with bloated object with unnecessary functionality.
  • By inheriting we can limit the scope of our functions using protected, this limits access to our function more than a public utility method would. So caller's know that if they want to use this method, they have to inherit which means that the subclass has a relationship with the base class and it's functionality.
  • If we used a public utility method, we're saying the caller can more freely use the method in different places without necessarily considering the relationship.

{

protected string GetFromResource(string fileName)
{
if (fileName == null)
throw new ArgumentNullException(nameof(fileName));

var assembly = Assembly.GetExecutingAssembly();
var resourceName = assembly.GetManifestResourceNames().First(x => x.Contains(fileName));

using (var file = assembly.GetManifestResourceStream(resourceName))
{
if (file == null)
Assert.Fail();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would throw here, instead of using Assert not sure how it works in c# but some test suites will report test failures different from unexpected exceptions, and not having an expected fixture file is probably in the latter category

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good intuition, Assert.Fail() actually just ends up throwing an AssertionException (it has some additional context handling).


using (var sr = new StreamReader(file))
{
var json = sr.ReadToEnd();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i don't think the thing its parsing has to be json. i would just do return sr.ReadToEnd()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good insight.

return json;
}
}
}
}
}
Loading